Add minimalistic protected-headers support
[gpgol.git] / src / mlang-charset.cpp
1 /* @file mlang-charset.cpp
2  * @brief Convert between charsets using Mlang
3  *
4  * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
5  * Software engineering by Intevation GmbH
6  *
7  * This file is part of GpgOL.
8  *
9  * GpgOL is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * GpgOL is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, see <http://www.gnu.org/licenses/>.
21  */
22
23 #include "config.h"
24 #include "common.h"
25 #define INITGUID
26 #include <initguid.h>
27 DEFINE_GUID (IID_IMultiLanguage, 0x275c23e1,0x3747,0x11d0,0x9f,
28                                  0xea,0x00,0xaa,0x00,0x3f,0x86,0x46);
29 #include <mlang.h>
30 #undef INITGUID
31
32 #include "dialogs.h"
33 #include "dispcache.h"
34
35 #include "mlang-charset.h"
36
37 static char *
38 iconv_to_utf8 (const char *charset, const char *input)
39 {
40   if (!charset || !input)
41     {
42       STRANGEPOINT;
43       return nullptr;
44     }
45
46   gpgrt_w32_iconv_t ctx = gpgrt_w32_iconv_open ("UTF-8", charset);
47   if (!ctx || ctx == (gpgrt_w32_iconv_t)-1)
48     {
49       log_debug ("%s:%s: Failed to open iconv ctx for '%s'",
50                  SRCNAME, __func__, charset);
51       return nullptr;
52     }
53
54   size_t len = 0;
55
56   for (const unsigned char *s = (const unsigned char*) input; *s; s++)
57     {
58       len++;
59       if ((*s & 0x80))
60         {
61           len += 5; /* We may need up to 6 bytes for the utf8 output. */
62         }
63     }
64
65   char *buffer = (char*) xmalloc (len + 1);
66   size_t inlen = strlen (input) + 1; // Need to add 1 for the zero
67   char *outptr = buffer;
68   size_t outbytes = len;
69   size_t ret = gpgrt_w32_iconv (ctx, (const char **)&input, &inlen,
70                                 &outptr, &outbytes);
71   gpgrt_w32_iconv_close (ctx);
72   if (ret == -1)
73     {
74       log_error ("%s:%s: Conversion failed for '%s'",
75                  SRCNAME, __func__, charset);
76       xfree (buffer);
77       return nullptr;
78     }
79   return buffer;
80 }
81
82 char *ansi_charset_to_utf8 (const char *charset, const char *input,
83                             size_t inlen, int codepage)
84 {
85   LPMULTILANGUAGE multilang = NULL;
86   MIMECSETINFO mime_info;
87   HRESULT err;
88   DWORD enc;
89   DWORD mode = 0;
90   unsigned int wlen = 0,
91                uinlen = 0;
92   wchar_t *buf;
93   char *ret;
94
95   if ((!charset || !strlen (charset)) && !codepage)
96     {
97       log_debug ("%s:%s: No charset / codepage returning plain.",
98                  SRCNAME, __func__);
99       return xstrdup (input);
100     }
101
102   auto cache = DispCache::instance ();
103   LPDISPATCH cachedLang = cache->getDisp (DISPID_MLANG_CHARSET);
104
105   if (!cachedLang)
106     {
107       CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER,
108                        IID_IMultiLanguage, (void**)&multilang);
109       memdbg_addRef (multilang);
110       cache->addDisp (DISPID_MLANG_CHARSET, (LPDISPATCH) multilang);
111     }
112   else
113     {
114       multilang = (LPMULTILANGUAGE) cachedLang;
115     }
116
117
118   if (!multilang)
119     {
120       log_error ("%s:%s: Failed to get multilang obj.",
121                  SRCNAME, __func__);
122       return NULL;
123     }
124
125   if (inlen > UINT_MAX)
126     {
127       log_error ("%s:%s: Inlen too long. Bug.",
128                  SRCNAME, __func__);
129       return NULL;
130     }
131
132   uinlen = (unsigned int) inlen;
133
134   if (!codepage)
135     {
136       mime_info.uiCodePage = 0;
137       mime_info.uiInternetEncoding = 0;
138       BSTR w_charset = utf8_to_wchar (charset);
139       err = multilang->GetCharsetInfo (w_charset, &mime_info);
140       xfree (w_charset);
141       if (err != S_OK)
142         {
143           log_debug ("%s:%s: Failed to find charset for: %s fallback to iconv",
144                      SRCNAME, __func__, charset);
145           /* We only use this as a fallback as the old code was older and
146              known to work in most cases. */
147           ret = iconv_to_utf8 (charset, input);
148           if (ret)
149             {
150               return ret;
151             }
152
153           return xstrdup (input);
154         }
155       enc = (mime_info.uiInternetEncoding == 0) ? mime_info.uiCodePage :
156                                                   mime_info.uiInternetEncoding;
157     }
158   else
159     {
160       enc = codepage;
161     }
162
163   /** Get the size of the result */
164   err = multilang->ConvertStringToUnicode(&mode, enc, const_cast<char*>(input),
165                                           &uinlen, NULL, &wlen);
166   if (FAILED (err))
167     {
168       log_error ("%s:%s: Failed conversion.",
169                  SRCNAME, __func__);
170       return NULL;
171   }
172   buf = (wchar_t*) xmalloc(sizeof(wchar_t) * (wlen + 1));
173
174   err = multilang->ConvertStringToUnicode(&mode, enc, const_cast<char*>(input),
175                                           &uinlen, buf, &wlen);
176   if (FAILED (err))
177     {
178       log_error ("%s:%s: Failed conversion 2.",
179                  SRCNAME, __func__);
180       xfree (buf);
181       return NULL;
182     }
183   /* Doc is not clear if this is terminated. */
184   buf[wlen] = L'\0';
185
186   ret = wchar_to_utf8 (buf);
187   xfree (buf);
188   return ret;
189 }