xref: /curl/src/tool_doswin.c (revision f383a176)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "tool_setup.h"
25 
26 #if defined(_WIN32) || defined(MSDOS)
27 
28 #if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME)
29 #  include <libgen.h>
30 #endif
31 
32 #ifdef _WIN32
33 #  include <stdlib.h>
34 #  include <tlhelp32.h>
35 #  include "tool_cfgable.h"
36 #  include "tool_libinfo.h"
37 #endif
38 
39 #include "tool_bname.h"
40 #include "tool_doswin.h"
41 
42 #include "curlx.h"
43 #include "memdebug.h" /* keep this as LAST include */
44 
45 #ifdef _WIN32
46 #  undef  PATH_MAX
47 #  define PATH_MAX MAX_PATH
48 #endif
49 
50 #ifndef S_ISCHR
51 #  ifdef S_IFCHR
52 #    define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
53 #  else
54 #    define S_ISCHR(m) (0) /* cannot tell if file is a device */
55 #  endif
56 #endif
57 
58 #ifdef _WIN32
59 #  define _use_lfn(f) (1)   /* long filenames always available */
60 #elif !defined(__DJGPP__) || (__DJGPP__ < 2)  /* DJGPP 2.0 has _use_lfn() */
61 #  define _use_lfn(f) (0)  /* long filenames never available */
62 #elif defined(__DJGPP__)
63 #  include <fcntl.h>                /* _use_lfn(f) prototype */
64 #endif
65 
66 #ifdef MSDOS
67 /* only used by msdosify() */
68 static SANITIZEcode truncate_dryrun(const char *path,
69                                     const size_t truncate_pos);
70 static SANITIZEcode msdosify(char **const sanitized, const char *file_name,
71                              int flags);
72 #endif
73 static SANITIZEcode rename_if_reserved_dos(char **const sanitized,
74                                            const char *file_name,
75                                            int flags);
76 
77 
78 /*
79 Sanitize a file or path name.
80 
81 All banned characters are replaced by underscores, for example:
82 f?*foo => f__foo
83 f:foo::$DATA => f_foo__$DATA
84 f:\foo:bar => f__foo_bar
85 f:\foo:bar => f:\foo:bar   (flag SANITIZE_ALLOW_PATH)
86 
87 This function was implemented according to the guidelines in 'Naming Files,
88 Paths, and Namespaces' section 'Naming Conventions'.
89 https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
90 
91 Flags
92 -----
93 SANITIZE_ALLOW_PATH:       Allow path separators and colons.
94 Without this flag path separators and colons are sanitized.
95 
96 SANITIZE_ALLOW_RESERVED:   Allow reserved device names.
97 Without this flag a reserved device name is renamed (COM1 => _COM1) unless it
98 is in a UNC prefixed path.
99 
100 Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
101 Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
102 */
sanitize_file_name(char ** const sanitized,const char * file_name,int flags)103 SANITIZEcode sanitize_file_name(char **const sanitized, const char *file_name,
104                                 int flags)
105 {
106   char *p, *target;
107   size_t len;
108   SANITIZEcode sc;
109   size_t max_sanitized_len;
110 
111   if(!sanitized)
112     return SANITIZE_ERR_BAD_ARGUMENT;
113 
114   *sanitized = NULL;
115 
116   if(!file_name)
117     return SANITIZE_ERR_BAD_ARGUMENT;
118 
119   if(flags & SANITIZE_ALLOW_PATH) {
120 #ifndef MSDOS
121     if(file_name[0] == '\\' && file_name[1] == '\\')
122       /* UNC prefixed path \\ (eg \\?\C:\foo) */
123       max_sanitized_len = 32767-1;
124     else
125 #endif
126       max_sanitized_len = PATH_MAX-1;
127   }
128   else
129     /* The maximum length of a filename. FILENAME_MAX is often the same as
130        PATH_MAX, in other words it is 260 and does not discount the path
131        information therefore we should not use it. */
132     max_sanitized_len = (PATH_MAX-1 > 255) ? 255 : PATH_MAX-1;
133 
134   len = strlen(file_name);
135   if(len > max_sanitized_len)
136     return SANITIZE_ERR_INVALID_PATH;
137 
138   target = strdup(file_name);
139   if(!target)
140     return SANITIZE_ERR_OUT_OF_MEMORY;
141 
142 #ifndef MSDOS
143   if((flags & SANITIZE_ALLOW_PATH) && !strncmp(target, "\\\\?\\", 4))
144     /* Skip the literal path prefix \\?\ */
145     p = target + 4;
146   else
147 #endif
148     p = target;
149 
150   /* replace control characters and other banned characters */
151   for(; *p; ++p) {
152     const char *banned;
153 
154     if((1 <= *p && *p <= 31) ||
155        (!(flags & SANITIZE_ALLOW_PATH) && *p == ':') ||
156        (!(flags & SANITIZE_ALLOW_PATH) && (*p == '/' || *p == '\\'))) {
157       *p = '_';
158       continue;
159     }
160 
161     for(banned = "|<>\"?*"; *banned; ++banned) {
162       if(*p == *banned) {
163         *p = '_';
164         break;
165       }
166     }
167   }
168 
169   /* remove trailing spaces and periods if not allowing paths */
170   if(!(flags & SANITIZE_ALLOW_PATH) && len) {
171     char *clip = NULL;
172 
173     p = &target[len];
174     do {
175       --p;
176       if(*p != ' ' && *p != '.')
177         break;
178       clip = p;
179     } while(p != target);
180 
181     if(clip) {
182       *clip = '\0';
183       len = clip - target;
184     }
185   }
186 
187 #ifdef MSDOS
188   sc = msdosify(&p, target, flags);
189   free(target);
190   if(sc)
191     return sc;
192   target = p;
193   len = strlen(target);
194 
195   if(len > max_sanitized_len) {
196     free(target);
197     return SANITIZE_ERR_INVALID_PATH;
198   }
199 #endif
200 
201   if(!(flags & SANITIZE_ALLOW_RESERVED)) {
202     sc = rename_if_reserved_dos(&p, target, flags);
203     free(target);
204     if(sc)
205       return sc;
206     target = p;
207     len = strlen(target);
208 
209     if(len > max_sanitized_len) {
210       free(target);
211       return SANITIZE_ERR_INVALID_PATH;
212     }
213   }
214 
215   *sanitized = target;
216   return SANITIZE_ERR_OK;
217 }
218 
219 #if defined(MSDOS)
220 /*
221 Test if truncating a path to a file will leave at least a single character in
222 the filename. Filenames suffixed by an alternate data stream cannot be
223 truncated. This performs a dry run, nothing is modified.
224 
225 Good truncate_pos 9:    C:\foo\bar  =>  C:\foo\ba
226 Good truncate_pos 6:    C:\foo      =>  C:\foo
227 Good truncate_pos 5:    C:\foo      =>  C:\fo
228 Bad* truncate_pos 5:    C:foo       =>  C:foo
229 Bad truncate_pos 5:     C:\foo:ads  =>  C:\fo
230 Bad truncate_pos 9:     C:\foo:ads  =>  C:\foo:ad
231 Bad truncate_pos 5:     C:\foo\bar  =>  C:\fo
232 Bad truncate_pos 5:     C:\foo\     =>  C:\fo
233 Bad truncate_pos 7:     C:\foo\     =>  C:\foo\
234 Error truncate_pos 7:   C:\foo      =>  (pos out of range)
235 Bad truncate_pos 1:     C:\foo\     =>  C
236 
237 * C:foo is ambiguous, C could end up being a drive or file therefore something
238   like C:superlongfilename cannot be truncated.
239 
240 Returns
241 SANITIZE_ERR_OK: Good -- 'path' can be truncated
242 SANITIZE_ERR_INVALID_PATH: Bad -- 'path' cannot be truncated
243 != SANITIZE_ERR_OK && != SANITIZE_ERR_INVALID_PATH: Error
244 */
truncate_dryrun(const char * path,const size_t truncate_pos)245 SANITIZEcode truncate_dryrun(const char *path, const size_t truncate_pos)
246 {
247   size_t len;
248 
249   if(!path)
250     return SANITIZE_ERR_BAD_ARGUMENT;
251 
252   len = strlen(path);
253 
254   if(truncate_pos > len)
255     return SANITIZE_ERR_BAD_ARGUMENT;
256 
257   if(!len || !truncate_pos)
258     return SANITIZE_ERR_INVALID_PATH;
259 
260   if(strpbrk(&path[truncate_pos - 1], "\\/:"))
261     return SANITIZE_ERR_INVALID_PATH;
262 
263   /* C:\foo can be truncated but C:\foo:ads cannot */
264   if(truncate_pos > 1) {
265     const char *p = &path[truncate_pos - 1];
266     do {
267       --p;
268       if(*p == ':')
269         return SANITIZE_ERR_INVALID_PATH;
270     } while(p != path && *p != '\\' && *p != '/');
271   }
272 
273   return SANITIZE_ERR_OK;
274 }
275 
276 /* The functions msdosify, rename_if_dos_device_name and __crt0_glob_function
277  * were taken with modification from the DJGPP port of tar 1.12. They use
278  * algorithms originally from DJTAR.
279  */
280 
281 /*
282 Extra sanitization MS-DOS for file_name.
283 
284 This is a supporting function for sanitize_file_name.
285 
286 Warning: This is an MS-DOS legacy function and was purposely written in a way
287 that some path information may pass through. For example drive letter names
288 (C:, D:, etc) are allowed to pass through. For sanitizing a filename use
289 sanitize_file_name.
290 
291 Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
292 Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
293 */
msdosify(char ** const sanitized,const char * file_name,int flags)294 SANITIZEcode msdosify(char **const sanitized, const char *file_name,
295                       int flags)
296 {
297   char dos_name[PATH_MAX];
298   static const char illegal_chars_dos[] = ".+, ;=[]" /* illegal in DOS */
299     "|<>/\\\":?*"; /* illegal in DOS & W95 */
300   static const char *illegal_chars_w95 = &illegal_chars_dos[8];
301   int idx, dot_idx;
302   const char *s = file_name;
303   char *d = dos_name;
304   const char *const dlimit = dos_name + sizeof(dos_name) - 1;
305   const char *illegal_aliens = illegal_chars_dos;
306   size_t len = sizeof(illegal_chars_dos) - 1;
307 
308   if(!sanitized)
309     return SANITIZE_ERR_BAD_ARGUMENT;
310 
311   *sanitized = NULL;
312 
313   if(!file_name)
314     return SANITIZE_ERR_BAD_ARGUMENT;
315 
316   if(strlen(file_name) > PATH_MAX-1)
317     return SANITIZE_ERR_INVALID_PATH;
318 
319   /* Support for Windows 9X VFAT systems, when available. */
320   if(_use_lfn(file_name)) {
321     illegal_aliens = illegal_chars_w95;
322     len -= (illegal_chars_w95 - illegal_chars_dos);
323   }
324 
325   /* Get past the drive letter, if any. */
326   if(s[0] >= 'A' && s[0] <= 'z' && s[1] == ':') {
327     *d++ = *s++;
328     *d = ((flags & SANITIZE_ALLOW_PATH)) ? ':' : '_';
329     ++d; ++s;
330   }
331 
332   for(idx = 0, dot_idx = -1; *s && d < dlimit; s++, d++) {
333     if(memchr(illegal_aliens, *s, len)) {
334 
335       if((flags & SANITIZE_ALLOW_PATH) && *s == ':')
336         *d = ':';
337       else if((flags & SANITIZE_ALLOW_PATH) && (*s == '/' || *s == '\\'))
338         *d = *s;
339       /* Dots are special: DOS does not allow them as the leading character,
340          and a filename cannot have more than a single dot. We leave the
341          first non-leading dot alone, unless it comes too close to the
342          beginning of the name: we want sh.lex.c to become sh_lex.c, not
343          sh.lex-c.  */
344       else if(*s == '.') {
345         if((flags & SANITIZE_ALLOW_PATH) && idx == 0 &&
346            (s[1] == '/' || s[1] == '\\' ||
347             (s[1] == '.' && (s[2] == '/' || s[2] == '\\')))) {
348           /* Copy "./" and "../" verbatim.  */
349           *d++ = *s++;
350           if(d == dlimit)
351             break;
352           if(*s == '.') {
353             *d++ = *s++;
354             if(d == dlimit)
355               break;
356           }
357           *d = *s;
358         }
359         else if(idx == 0)
360           *d = '_';
361         else if(dot_idx >= 0) {
362           if(dot_idx < 5) { /* 5 is a heuristic ad-hoc'ery */
363             d[dot_idx - idx] = '_'; /* replace previous dot */
364             *d = '.';
365           }
366           else
367             *d = '-';
368         }
369         else
370           *d = '.';
371 
372         if(*s == '.')
373           dot_idx = idx;
374       }
375       else if(*s == '+' && s[1] == '+') {
376         if(idx - 2 == dot_idx) { /* .c++, .h++ etc. */
377           *d++ = 'x';
378           if(d == dlimit)
379             break;
380           *d   = 'x';
381         }
382         else {
383           /* libg++ etc.  */
384           if(dlimit - d < 4) {
385             *d++ = 'x';
386             if(d == dlimit)
387               break;
388             *d   = 'x';
389           }
390           else {
391             memcpy(d, "plus", 4);
392             d += 3;
393           }
394         }
395         s++;
396         idx++;
397       }
398       else
399         *d = '_';
400     }
401     else
402       *d = *s;
403     if(*s == '/' || *s == '\\') {
404       idx = 0;
405       dot_idx = -1;
406     }
407     else
408       idx++;
409   }
410   *d = '\0';
411 
412   if(*s) {
413     /* dos_name is truncated, check that truncation requirements are met,
414        specifically truncating a filename suffixed by an alternate data stream
415        or truncating the entire filename is not allowed. */
416     if(strpbrk(s, "\\/:") || truncate_dryrun(dos_name, d - dos_name))
417       return SANITIZE_ERR_INVALID_PATH;
418   }
419 
420   *sanitized = strdup(dos_name);
421   return (*sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY);
422 }
423 #endif /* MSDOS */
424 
425 /*
426 Rename file_name if it is a reserved dos device name.
427 
428 This is a supporting function for sanitize_file_name.
429 
430 Warning: This is an MS-DOS legacy function and was purposely written in a way
431 that some path information may pass through. For example drive letter names
432 (C:, D:, etc) are allowed to pass through. For sanitizing a filename use
433 sanitize_file_name.
434 
435 Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
436 Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
437 */
rename_if_reserved_dos(char ** const sanitized,const char * file_name,int flags)438 static SANITIZEcode rename_if_reserved_dos(char **const sanitized,
439                                            const char *file_name,
440                                            int flags)
441 {
442   /* We could have a file whose name is a device on MS-DOS. Trying to
443    * retrieve such a file would fail at best and wedge us at worst. We need
444    * to rename such files. */
445   char *p, *base;
446   char fname[PATH_MAX];
447 #ifdef MSDOS
448   struct_stat st_buf;
449 #endif
450   size_t len;
451 
452   if(!sanitized || !file_name)
453     return SANITIZE_ERR_BAD_ARGUMENT;
454 
455   *sanitized = NULL;
456   len = strlen(file_name);
457 
458   /* Ignore UNC prefixed paths, they are allowed to contain a reserved name. */
459 #ifndef MSDOS
460   if((flags & SANITIZE_ALLOW_PATH) &&
461      file_name[0] == '\\' && file_name[1] == '\\') {
462     *sanitized = strdup(file_name);
463     if(!*sanitized)
464       return SANITIZE_ERR_OUT_OF_MEMORY;
465     return SANITIZE_ERR_OK;
466   }
467 #endif
468 
469   if(len > PATH_MAX-1)
470     return SANITIZE_ERR_INVALID_PATH;
471 
472   memcpy(fname, file_name, len);
473   fname[len] = '\0';
474   base = basename(fname);
475 
476   /* Rename reserved device names that are known to be accessible without \\.\
477      Examples: CON => _CON, CON.EXT => CON_EXT, CON:ADS => CON_ADS
478      https://support.microsoft.com/en-us/kb/74496
479      https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
480      */
481   for(p = fname; p; p = (p == fname && fname != base ? base : NULL)) {
482     size_t p_len;
483     int x = (curl_strnequal(p, "CON", 3) ||
484              curl_strnequal(p, "PRN", 3) ||
485              curl_strnequal(p, "AUX", 3) ||
486              curl_strnequal(p, "NUL", 3)) ? 3 :
487             (curl_strnequal(p, "CLOCK$", 6)) ? 6 :
488             (curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ?
489               (('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0;
490 
491     if(!x)
492       continue;
493 
494     /* the devices may be accessible with an extension or ADS, for
495        example CON.AIR and 'CON . AIR' and CON:AIR access console */
496 
497     for(; p[x] == ' '; ++x)
498       ;
499 
500     if(p[x] == '.') {
501       p[x] = '_';
502       continue;
503     }
504     else if(p[x] == ':') {
505       if(!(flags & SANITIZE_ALLOW_PATH)) {
506         p[x] = '_';
507         continue;
508       }
509       ++x;
510     }
511     else if(p[x]) /* no match */
512       continue;
513 
514     /* p points to 'CON' or 'CON ' or 'CON:', etc */
515     p_len = strlen(p);
516 
517     /* Prepend a '_' */
518     if(strlen(fname) == PATH_MAX-1)
519       return SANITIZE_ERR_INVALID_PATH;
520     memmove(p + 1, p, p_len + 1);
521     p[0] = '_';
522     ++p_len;
523 
524     /* if fname was just modified then the basename pointer must be updated */
525     if(p == fname)
526       base = basename(fname);
527   }
528 
529   /* This is the legacy portion from rename_if_dos_device_name that checks for
530      reserved device names. It only works on MS-DOS. On Windows XP the stat
531      check errors with EINVAL if the device name is reserved. On Windows
532      Vista/7/8 it sets mode S_IFREG (regular file or device). According to
533      MSDN stat doc the latter behavior is correct, but that does not help us
534      identify whether it is a reserved device name and not a regular
535      filename. */
536 #ifdef MSDOS
537   if(base && ((stat(base, &st_buf)) == 0) && (S_ISCHR(st_buf.st_mode))) {
538     /* Prepend a '_' */
539     size_t blen = strlen(base);
540     if(blen) {
541       if(strlen(fname) >= PATH_MAX-1)
542         return SANITIZE_ERR_INVALID_PATH;
543       memmove(base + 1, base, blen + 1);
544       base[0] = '_';
545     }
546   }
547 #endif
548 
549   *sanitized = strdup(fname);
550   return (*sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY);
551 }
552 
553 #if defined(MSDOS) && (defined(__DJGPP__) || defined(__GO32__))
554 
555 /*
556  * Disable program default argument globbing. We do it on our own.
557  */
__crt0_glob_function(char * arg)558 char **__crt0_glob_function(char *arg)
559 {
560   (void)arg;
561   return (char **)0;
562 }
563 
564 #endif /* MSDOS && (__DJGPP__ || __GO32__) */
565 
566 #ifdef _WIN32
567 
568 #if !defined(CURL_WINDOWS_UWP) && \
569   !defined(CURL_DISABLE_CA_SEARCH) && !defined(CURL_CA_SEARCH_SAFE)
570 /* Search and set the CA cert file for Windows.
571  *
572  * Do not call this function if Schannel is the selected SSL backend. We allow
573  * setting CA location for Schannel only when explicitly specified by the user
574  * via CURLOPT_CAINFO / --cacert.
575  *
576  * Function to find CACert bundle on a Win32 platform using SearchPath.
577  * (SearchPath is already declared via inclusions done in setup header file)
578  * (Use the ASCII version instead of the Unicode one!)
579  * The order of the directories it searches is:
580  *  1. application's directory
581  *  2. current working directory
582  *  3. Windows System directory (e.g. C:\Windows\System32)
583  *  4. Windows Directory (e.g. C:\Windows)
584  *  5. all directories along %PATH%
585  *
586  * For WinXP and later search order actually depends on registry value:
587  * HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeProcessSearchMode
588  */
FindWin32CACert(struct OperationConfig * config,const TCHAR * bundle_file)589 CURLcode FindWin32CACert(struct OperationConfig *config,
590                          const TCHAR *bundle_file)
591 {
592   CURLcode result = CURLE_OK;
593   DWORD res_len;
594   TCHAR buf[PATH_MAX];
595   TCHAR *ptr = NULL;
596 
597   buf[0] = TEXT('\0');
598 
599   res_len = SearchPath(NULL, bundle_file, NULL, PATH_MAX, buf, &ptr);
600   if(res_len > 0) {
601     char *mstr = curlx_convert_tchar_to_UTF8(buf);
602     Curl_safefree(config->cacert);
603     if(mstr)
604       config->cacert = strdup(mstr);
605     curlx_unicodefree(mstr);
606     if(!config->cacert)
607       result = CURLE_OUT_OF_MEMORY;
608   }
609 
610   return result;
611 }
612 #endif
613 
614 /* Get a list of all loaded modules with full paths.
615  * Returns slist on success or NULL on error.
616  */
GetLoadedModulePaths(void)617 struct curl_slist *GetLoadedModulePaths(void)
618 {
619   HANDLE hnd = INVALID_HANDLE_VALUE;
620   MODULEENTRY32 mod = {0};
621   struct curl_slist *slist = NULL;
622 
623   mod.dwSize = sizeof(MODULEENTRY32);
624 
625   do {
626     hnd = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
627   } while(hnd == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
628 
629   if(hnd == INVALID_HANDLE_VALUE)
630     goto error;
631 
632   if(!Module32First(hnd, &mod))
633     goto error;
634 
635   do {
636     char *path; /* points to stack allocated buffer */
637     struct curl_slist *temp;
638 
639 #ifdef UNICODE
640     /* sizeof(mod.szExePath) is the max total bytes of wchars. the max total
641        bytes of multibyte chars will not be more than twice that. */
642     char buffer[sizeof(mod.szExePath) * 2];
643     if(!WideCharToMultiByte(CP_ACP, 0, mod.szExePath, -1,
644                             buffer, sizeof(buffer), NULL, NULL))
645       goto error;
646     path = buffer;
647 #else
648     path = mod.szExePath;
649 #endif
650     temp = curl_slist_append(slist, path);
651     if(!temp)
652       goto error;
653     slist = temp;
654   } while(Module32Next(hnd, &mod));
655 
656   goto cleanup;
657 
658 error:
659   curl_slist_free_all(slist);
660   slist = NULL;
661 cleanup:
662   if(hnd != INVALID_HANDLE_VALUE)
663     CloseHandle(hnd);
664   return slist;
665 }
666 
667 bool tool_term_has_bold;
668 
669 #ifndef CURL_WINDOWS_UWP
670 /* The terminal settings to restore on exit */
671 static struct TerminalSettings {
672   HANDLE hStdOut;
673   DWORD dwOutputMode;
674   LONG valid;
675 } TerminalSettings;
676 
677 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
678 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
679 #endif
680 
restore_terminal(void)681 static void restore_terminal(void)
682 {
683   if(InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE))
684     SetConsoleMode(TerminalSettings.hStdOut, TerminalSettings.dwOutputMode);
685 }
686 
687 /* This is the console signal handler.
688  * The system calls it in a separate thread.
689  */
signal_handler(DWORD type)690 static BOOL WINAPI signal_handler(DWORD type)
691 {
692   if(type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT)
693     restore_terminal();
694   return FALSE;
695 }
696 
init_terminal(void)697 static void init_terminal(void)
698 {
699   TerminalSettings.hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
700 
701   /*
702    * Enable VT (Virtual Terminal) output.
703    * Note: VT mode flag can be set on any version of Windows, but VT
704    * processing only performed on Win10 >= version 1709 (OS build 16299)
705    * Creator's Update. Also, ANSI bold on/off supported since then.
706    */
707   if(TerminalSettings.hStdOut == INVALID_HANDLE_VALUE ||
708      !GetConsoleMode(TerminalSettings.hStdOut,
709                      &TerminalSettings.dwOutputMode) ||
710      !curlx_verify_windows_version(10, 0, 16299, PLATFORM_WINNT,
711                                    VERSION_GREATER_THAN_EQUAL))
712     return;
713 
714   if((TerminalSettings.dwOutputMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))
715     tool_term_has_bold = true;
716   else {
717     /* The signal handler is set before attempting to change the console mode
718        because otherwise a signal would not be caught after the change but
719        before the handler was installed. */
720     (void)InterlockedExchange(&TerminalSettings.valid, (LONG)TRUE);
721     if(SetConsoleCtrlHandler(signal_handler, TRUE)) {
722       if(SetConsoleMode(TerminalSettings.hStdOut,
723                         (TerminalSettings.dwOutputMode |
724                          ENABLE_VIRTUAL_TERMINAL_PROCESSING))) {
725         tool_term_has_bold = true;
726         atexit(restore_terminal);
727       }
728       else {
729         SetConsoleCtrlHandler(signal_handler, FALSE);
730         (void)InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE);
731       }
732     }
733   }
734 }
735 #endif
736 
737 LARGE_INTEGER tool_freq;
738 bool tool_isVistaOrGreater;
739 
win32_init(void)740 CURLcode win32_init(void)
741 {
742   /* curlx_verify_windows_version must be called during init at least once
743      because it has its own initialization routine. */
744   if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
745                                   VERSION_GREATER_THAN_EQUAL))
746     tool_isVistaOrGreater = true;
747   else
748     tool_isVistaOrGreater = false;
749 
750   QueryPerformanceFrequency(&tool_freq);
751 
752 #ifndef CURL_WINDOWS_UWP
753   init_terminal();
754 #endif
755 
756   return CURLE_OK;
757 }
758 
759 #endif /* _WIN32 */
760 
761 #endif /* _WIN32 || MSDOS */
762