xref: /curl/src/tool_doswin.c (revision e9a7d4a1)
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   /* Search and set cert file only if libcurl supports SSL.
624    *
625    * If Schannel is the selected SSL backend then these locations are
626    * ignored. We allow setting CA location for schannel only when explicitly
627    * specified by the user via CURLOPT_CAINFO / --cacert.
628    */
629   if(feature_ssl && backend != CURLSSLBACKEND_SCHANNEL) {
630 
631     DWORD res_len;
632     TCHAR buf[PATH_MAX];
633     TCHAR *ptr = NULL;
634 
635     buf[0] = TEXT('\0');
636 
637     res_len = SearchPath(NULL, bundle_file, NULL, PATH_MAX, buf, &ptr);
638     if(res_len > 0) {
639       char *mstr = curlx_convert_tchar_to_UTF8(buf);
640       Curl_safefree(config->cacert);
641       if(mstr)
642         config->cacert = strdup(mstr);
643       curlx_unicodefree(mstr);
644       if(!config->cacert)
645         result = CURLE_OUT_OF_MEMORY;
646     }
647   }
648 
649   return result;
650 }
651 
652 
653 /* Get a list of all loaded modules with full paths.
654  * Returns slist on success or NULL on error.
655  */
GetLoadedModulePaths(void)656 struct curl_slist *GetLoadedModulePaths(void)
657 {
658   HANDLE hnd = INVALID_HANDLE_VALUE;
659   MODULEENTRY32 mod = {0};
660   struct curl_slist *slist = NULL;
661 
662   mod.dwSize = sizeof(MODULEENTRY32);
663 
664   do {
665     hnd = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
666   } while(hnd == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
667 
668   if(hnd == INVALID_HANDLE_VALUE)
669     goto error;
670 
671   if(!Module32First(hnd, &mod))
672     goto error;
673 
674   do {
675     char *path; /* points to stack allocated buffer */
676     struct curl_slist *temp;
677 
678 #ifdef UNICODE
679     /* sizeof(mod.szExePath) is the max total bytes of wchars. the max total
680        bytes of multibyte chars won't be more than twice that. */
681     char buffer[sizeof(mod.szExePath) * 2];
682     if(!WideCharToMultiByte(CP_ACP, 0, mod.szExePath, -1,
683                             buffer, sizeof(buffer), NULL, NULL))
684       goto error;
685     path = buffer;
686 #else
687     path = mod.szExePath;
688 #endif
689     temp = curl_slist_append(slist, path);
690     if(!temp)
691       goto error;
692     slist = temp;
693   } while(Module32Next(hnd, &mod));
694 
695   goto cleanup;
696 
697 error:
698   curl_slist_free_all(slist);
699   slist = NULL;
700 cleanup:
701   if(hnd != INVALID_HANDLE_VALUE)
702     CloseHandle(hnd);
703   return slist;
704 }
705 
706 /* The terminal settings to restore on exit */
707 static struct TerminalSettings {
708   HANDLE hStdOut;
709   DWORD dwOutputMode;
710   LONG valid;
711 } TerminalSettings;
712 
713 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
714 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
715 #endif
716 
717 bool tool_term_has_bold;
718 
restore_terminal(void)719 static void restore_terminal(void)
720 {
721   if(InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE))
722     SetConsoleMode(TerminalSettings.hStdOut, TerminalSettings.dwOutputMode);
723 }
724 
725 /* This is the console signal handler.
726  * The system calls it in a separate thread.
727  */
signal_handler(DWORD type)728 static BOOL WINAPI signal_handler(DWORD type)
729 {
730   if(type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT)
731     restore_terminal();
732   return FALSE;
733 }
734 
init_terminal(void)735 static void init_terminal(void)
736 {
737   TerminalSettings.hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
738 
739   /*
740    * Enable VT (Virtual Terminal) output.
741    * Note: VT mode flag can be set on any version of Windows, but VT
742    * processing only performed on Win10 >= version 1709 (OS build 16299)
743    * Creator's Update. Also, ANSI bold on/off supported since then.
744    */
745   if(TerminalSettings.hStdOut == INVALID_HANDLE_VALUE ||
746      !GetConsoleMode(TerminalSettings.hStdOut,
747                      &TerminalSettings.dwOutputMode) ||
748      !curlx_verify_windows_version(10, 0, 16299, PLATFORM_WINNT,
749                                    VERSION_GREATER_THAN_EQUAL))
750     return;
751 
752   if((TerminalSettings.dwOutputMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))
753     tool_term_has_bold = true;
754   else {
755     /* The signal handler is set before attempting to change the console mode
756        because otherwise a signal would not be caught after the change but
757        before the handler was installed. */
758     (void)InterlockedExchange(&TerminalSettings.valid, (LONG)TRUE);
759     if(SetConsoleCtrlHandler(signal_handler, TRUE)) {
760       if(SetConsoleMode(TerminalSettings.hStdOut,
761                         (TerminalSettings.dwOutputMode |
762                          ENABLE_VIRTUAL_TERMINAL_PROCESSING))) {
763         tool_term_has_bold = true;
764         atexit(restore_terminal);
765       }
766       else {
767         SetConsoleCtrlHandler(signal_handler, FALSE);
768         (void)InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE);
769       }
770     }
771   }
772 }
773 
774 LARGE_INTEGER tool_freq;
775 bool tool_isVistaOrGreater;
776 
win32_init(void)777 CURLcode win32_init(void)
778 {
779   /* curlx_verify_windows_version must be called during init at least once
780      because it has its own initialization routine. */
781   if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
782                                   VERSION_GREATER_THAN_EQUAL))
783     tool_isVistaOrGreater = true;
784   else
785     tool_isVistaOrGreater = false;
786 
787   QueryPerformanceFrequency(&tool_freq);
788 
789   init_terminal();
790 
791   return CURLE_OK;
792 }
793 
794 #endif /* _WIN32 */
795 
796 #endif /* _WIN32 || MSDOS */
797