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