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