xref: /freebsd/crypto/openssl/apps/lib/win32_init.c (revision 10a428653ee7216475f1ddce3fb4cbf1200319f8)
1 /*
2  * Copyright 2016-2026 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9 
10 #include <windows.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #if defined(CP_UTF8)
15 
16 static UINT saved_cp;
17 static int newargc;
18 static char **newargv;
19 
cleanup(void)20 static void cleanup(void)
21 {
22     int i;
23 
24     SetConsoleOutputCP(saved_cp);
25 
26     for (i = 0; i < newargc; i++)
27         free(newargv[i]);
28 
29     free(newargv);
30 }
31 
32 /*
33  * Incrementally [re]allocate newargv and keep it NULL-terminated.
34  */
validate_argv(int argc)35 static int validate_argv(int argc)
36 {
37     static int size = 0;
38 
39     if (argc >= size) {
40         char **ptr;
41 
42         while (argc >= size)
43             size += 64;
44 
45         ptr = realloc(newargv, size * sizeof(newargv[0]));
46         if (ptr == NULL)
47             return 0;
48 
49         (newargv = ptr)[argc] = NULL;
50     } else {
51         newargv[argc] = NULL;
52     }
53 
54     return 1;
55 }
56 
process_glob(WCHAR * wstr,int wlen)57 static int process_glob(WCHAR *wstr, int wlen)
58 {
59     int i, slash, udlen;
60     WCHAR saved_char;
61     WIN32_FIND_DATAW data;
62     HANDLE h;
63 
64     /*
65      * Note that we support wildcard characters only in filename part
66      * of the path, and not in directories. Windows users are used to
67      * this, that's why recursive glob processing is not implemented.
68      */
69     /*
70      * Start by looking for last slash or backslash, ...
71      */
72     for (slash = 0, i = 0; i < wlen; i++)
73         if (wstr[i] == L'/' || wstr[i] == L'\\')
74             slash = i + 1;
75     /*
76      * ... then look for asterisk or question mark in the file name.
77      */
78     for (i = slash; i < wlen; i++)
79         if (wstr[i] == L'*' || wstr[i] == L'?')
80             break;
81 
82     if (i == wlen)
83         return 0; /* definitely not a glob */
84 
85     saved_char = wstr[wlen];
86     wstr[wlen] = L'\0';
87     h = FindFirstFileW(wstr, &data);
88     wstr[wlen] = saved_char;
89     if (h == INVALID_HANDLE_VALUE)
90         return 0; /* not a valid glob, just pass... */
91 
92     if (slash)
93         udlen = WideCharToMultiByte(CP_UTF8, 0, wstr, slash,
94             NULL, 0, NULL, NULL);
95     else
96         udlen = 0;
97 
98     do {
99         int uflen;
100         char *arg;
101 
102         /*
103          * skip over . and ..
104          */
105         if (data.cFileName[0] == L'.') {
106             if ((data.cFileName[1] == L'\0') || (data.cFileName[1] == L'.' && data.cFileName[2] == L'\0'))
107                 continue;
108         }
109 
110         if (!validate_argv(newargc + 1))
111             break;
112 
113         /*
114          * -1 below means "scan for trailing '\0' *and* count it",
115          * so that |uflen| covers even trailing '\0'.
116          */
117         uflen = WideCharToMultiByte(CP_UTF8, 0, data.cFileName, -1,
118             NULL, 0, NULL, NULL);
119 
120         arg = malloc(udlen + uflen);
121         if (arg == NULL)
122             break;
123 
124         if (udlen)
125             WideCharToMultiByte(CP_UTF8, 0, wstr, slash,
126                 arg, udlen, NULL, NULL);
127 
128         WideCharToMultiByte(CP_UTF8, 0, data.cFileName, -1,
129             arg + udlen, uflen, NULL, NULL);
130 
131         newargv[newargc++] = arg;
132     } while (FindNextFileW(h, &data));
133 
134     CloseHandle(h);
135 
136     return 1;
137 }
138 
win32_utf8argv(int * argc,char ** argv[])139 void win32_utf8argv(int *argc, char **argv[])
140 {
141     const WCHAR *wcmdline;
142     WCHAR *warg, *wend, *p;
143     int wlen, ulen, valid = 1;
144     char *arg;
145 
146     if (GetEnvironmentVariableW(L"OPENSSL_WIN32_UTF8", NULL, 0) == 0)
147         return;
148 
149     newargc = 0;
150     newargv = NULL;
151     if (!validate_argv(newargc))
152         return;
153 
154     wcmdline = GetCommandLineW();
155     if (wcmdline == NULL)
156         return;
157 
158     /*
159      * make a copy of the command line, since we might have to modify it...
160      */
161     wlen = wcslen(wcmdline);
162     p = _alloca((wlen + 1) * sizeof(WCHAR));
163     wcscpy(p, wcmdline);
164 
165     while (*p != L'\0') {
166         int in_quote = 0;
167 
168         if (*p == L' ' || *p == L'\t') {
169             p++; /* skip over whitespace */
170             continue;
171         }
172 
173         /*
174          * Note: because we may need to fiddle with the number of backslashes,
175          * the argument string is copied into itself.  This is safe because
176          * the number of characters will never expand.
177          */
178         warg = wend = p;
179         while (*p != L'\0'
180             && (in_quote || (*p != L' ' && *p != L'\t'))) {
181             switch (*p) {
182             case L'\\':
183                 /*
184                  * Microsoft documentation on how backslashes are treated
185                  * is:
186                  *
187                  * + Backslashes are interpreted literally, unless they
188                  *   immediately precede a double quotation mark.
189                  * + If an even number of backslashes is followed by a double
190                  *   quotation mark, one backslash is placed in the argv array
191                  *   for every pair of backslashes, and the double quotation
192                  *   mark is interpreted as a string delimiter.
193                  * + If an odd number of backslashes is followed by a double
194                  *   quotation mark, one backslash is placed in the argv array
195                  *   for every pair of backslashes, and the double quotation
196                  *   mark is "escaped" by the remaining backslash, causing a
197                  *   literal double quotation mark (") to be placed in argv.
198                  *
199                  * Ref: https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
200                  *
201                  * Though referred page doesn't mention it, multiple qouble
202                  * quotes are also special. Pair of double quotes in quoted
203                  * string is counted as single double quote.
204                  */
205                 {
206                     const WCHAR *q = p;
207                     int i;
208 
209                     while (*p == L'\\')
210                         p++;
211 
212                     if (*p == L'"') {
213                         int i;
214 
215                         for (i = (p - q) / 2; i > 0; i--)
216                             *wend++ = L'\\';
217 
218                         /*
219                          * if odd amount of backslashes before the quote,
220                          * said quote is part of the argument, not a delimiter
221                          */
222                         if ((p - q) % 2 == 1)
223                             *wend++ = *p++;
224                     } else {
225                         for (i = p - q; i > 0; i--)
226                             *wend++ = L'\\';
227                     }
228                 }
229                 break;
230             case L'"':
231                 /*
232                  * Without the preceding backslash (or when preceded with an
233                  * even number of backslashes), the double quote is a simple
234                  * string delimiter and just slightly change the parsing state
235                  */
236                 if (in_quote && p[1] == L'"')
237                     *wend++ = *p++;
238                 else
239                     in_quote = !in_quote;
240                 p++;
241                 break;
242             default:
243                 /*
244                  * Any other non-delimiter character is just taken verbatim
245                  */
246                 *wend++ = *p++;
247             }
248         }
249 
250         wlen = wend - warg;
251 
252         if (wlen == 0 || !process_glob(warg, wlen)) {
253             if (!validate_argv(newargc + 1)) {
254                 valid = 0;
255                 break;
256             }
257 
258             ulen = 0;
259             if (wlen > 0) {
260                 ulen = WideCharToMultiByte(CP_UTF8, 0, warg, wlen,
261                     NULL, 0, NULL, NULL);
262                 if (ulen <= 0)
263                     continue;
264             }
265 
266             arg = malloc(ulen + 1);
267             if (arg == NULL) {
268                 valid = 0;
269                 break;
270             }
271 
272             if (wlen > 0)
273                 WideCharToMultiByte(CP_UTF8, 0, warg, wlen,
274                     arg, ulen, NULL, NULL);
275             arg[ulen] = '\0';
276 
277             newargv[newargc++] = arg;
278         }
279     }
280 
281     if (valid) {
282         saved_cp = GetConsoleOutputCP();
283         SetConsoleOutputCP(CP_UTF8);
284 
285         *argc = newargc;
286         *argv = newargv;
287 
288         atexit(cleanup);
289     } else if (newargv != NULL) {
290         int i;
291 
292         for (i = 0; i < newargc; i++)
293             free(newargv[i]);
294 
295         free(newargv);
296 
297         newargc = 0;
298         newargv = NULL;
299     }
300 
301     return;
302 }
303 #else
win32_utf8argv(int * argc,char ** argv[])304 void win32_utf8argv(int *argc, char **argv[])
305 {
306     return;
307 }
308 #endif
309