xref: /third_party/curl/src/tool_doswin.c (revision 13498266)
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
67static SANITIZEcode truncate_dryrun(const char *path,
68                                    const size_t truncate_pos);
69#ifdef MSDOS
70static SANITIZEcode msdosify(char **const sanitized, const char *file_name,
71                             int flags);
72#endif
73static 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/*
80Sanitize a file or path name.
81
82All banned characters are replaced by underscores, for example:
83f?*foo => f__foo
84f:foo::$DATA => f_foo__$DATA
85f:\foo:bar => f__foo_bar
86f:\foo:bar => f:\foo:bar   (flag SANITIZE_ALLOW_PATH)
87
88This function was implemented according to the guidelines in 'Naming Files,
89Paths, and Namespaces' section 'Naming Conventions'.
90https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
91
92Flags
93-----
94SANITIZE_ALLOW_COLONS:     Allow colons.
95Without this flag colons are sanitized.
96
97SANITIZE_ALLOW_PATH:       Allow path separators and colons.
98Without this flag path separators and colons are sanitized.
99
100SANITIZE_ALLOW_RESERVED:   Allow reserved device names.
101Without this flag a reserved device name is renamed (COM1 => _COM1) unless it's
102in a UNC prefixed path.
103
104SANITIZE_ALLOW_TRUNCATE:   Allow truncating a long filename.
105Without this flag if the sanitized filename or path will be too long an error
106occurs. With this flag the filename --and not any other parts of the path-- may
107be truncated to at least a single character. A filename followed by an
108alternate data stream (ADS) cannot be truncated in any case.
109
110Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
111Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
112*/
113SANITIZEcode 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/*
239Test if truncating a path to a file will leave at least a single character in
240the filename. Filenames suffixed by an alternate data stream can't be
241truncated. This performs a dry run, nothing is modified.
242
243Good truncate_pos 9:    C:\foo\bar  =>  C:\foo\ba
244Good truncate_pos 6:    C:\foo      =>  C:\foo
245Good truncate_pos 5:    C:\foo      =>  C:\fo
246Bad* truncate_pos 5:    C:foo       =>  C:foo
247Bad truncate_pos 5:     C:\foo:ads  =>  C:\fo
248Bad truncate_pos 9:     C:\foo:ads  =>  C:\foo:ad
249Bad truncate_pos 5:     C:\foo\bar  =>  C:\fo
250Bad truncate_pos 5:     C:\foo\     =>  C:\fo
251Bad truncate_pos 7:     C:\foo\     =>  C:\foo\
252Error truncate_pos 7:   C:\foo      =>  (pos out of range)
253Bad 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
258Returns
259SANITIZE_ERR_OK: Good -- 'path' can be truncated
260SANITIZE_ERR_INVALID_PATH: Bad -- 'path' cannot be truncated
261!= SANITIZE_ERR_OK && != SANITIZE_ERR_INVALID_PATH: Error
262*/
263SANITIZEcode 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/*
300Extra sanitization MSDOS for file_name.
301
302This is a supporting function for sanitize_file_name.
303
304Warning: This is an MSDOS legacy function and was purposely written in a way
305that 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
307sanitize_file_name.
308
309Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
310Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
311*/
312#if defined(MSDOS) || defined(UNITTESTS)
313SANITIZEcode 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/*
448Rename file_name if it's a reserved dos device name.
449
450This is a supporting function for sanitize_file_name.
451
452Warning: This is an MSDOS legacy function and was purposely written in a way
453that 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
455sanitize_file_name.
456
457Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
458Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
459*/
460SANITIZEcode 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 */
592char **__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
617CURLcode 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 */
656struct 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
697error:
698  curl_slist_free_all(slist);
699  slist = NULL;
700cleanup:
701  if(hnd != INVALID_HANDLE_VALUE)
702    CloseHandle(hnd);
703  return slist;
704}
705
706/* The terminal settings to restore on exit */
707static 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
717bool tool_term_has_bold;
718
719static 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 */
728static 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
735static 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
774LARGE_INTEGER tool_freq;
775bool tool_isVistaOrGreater;
776
777CURLcode 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