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