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