quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

tool_doswin.c (27808B)


      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 #include "tool_msgs.h"
     42 
     43 #include "memdebug.h" /* keep this as LAST include */
     44 
     45 #ifdef _WIN32
     46 #  undef  PATH_MAX
     47 #  define PATH_MAX MAX_PATH
     48 #elif !defined(__DJGPP__) || (__DJGPP__ < 2)  /* DJGPP 2.0 has _use_lfn() */
     49 #  define _use_lfn(f) (0)  /* long filenames never available */
     50 #elif defined(__DJGPP__)
     51 #  include <fcntl.h>       /* _use_lfn(f) prototype */
     52 #endif
     53 
     54 #ifdef MSDOS
     55 
     56 #ifndef S_ISCHR
     57 #  ifdef S_IFCHR
     58 #    define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
     59 #  else
     60 #    define S_ISCHR(m) (0) /* cannot tell if file is a device */
     61 #  endif
     62 #endif
     63 
     64 /* only used by msdosify() */
     65 static SANITIZEcode truncate_dryrun(const char *path,
     66                                     const size_t truncate_pos);
     67 static SANITIZEcode msdosify(char **const sanitized, const char *file_name,
     68                              int flags);
     69 #endif
     70 static SANITIZEcode rename_if_reserved_dos(char **const sanitized,
     71                                            const char *file_name,
     72                                            int flags);
     73 
     74 
     75 /*
     76 Sanitize a file or path name.
     77 
     78 All banned characters are replaced by underscores, for example:
     79 f?*foo => f__foo
     80 f:foo::$DATA => f_foo__$DATA
     81 f:\foo:bar => f__foo_bar
     82 f:\foo:bar => f:\foo:bar   (flag SANITIZE_ALLOW_PATH)
     83 
     84 This function was implemented according to the guidelines in 'Naming Files,
     85 Paths, and Namespaces' section 'Naming Conventions'.
     86 https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
     87 
     88 Flags
     89 -----
     90 SANITIZE_ALLOW_PATH:       Allow path separators and colons.
     91 Without this flag path separators and colons are sanitized.
     92 
     93 SANITIZE_ALLOW_RESERVED:   Allow reserved device names.
     94 Without this flag a reserved device name is renamed (COM1 => _COM1) unless it
     95 is in a UNC prefixed path.
     96 
     97 Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
     98 Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
     99 */
    100 SANITIZEcode sanitize_file_name(char **const sanitized, const char *file_name,
    101                                 int flags)
    102 {
    103   char *p, *target;
    104   size_t len;
    105   SANITIZEcode sc;
    106   size_t max_sanitized_len;
    107 
    108   if(!sanitized)
    109     return SANITIZE_ERR_BAD_ARGUMENT;
    110 
    111   *sanitized = NULL;
    112 
    113   if(!file_name)
    114     return SANITIZE_ERR_BAD_ARGUMENT;
    115 
    116   if(flags & SANITIZE_ALLOW_PATH) {
    117 #ifndef MSDOS
    118     if(file_name[0] == '\\' && file_name[1] == '\\')
    119       /* UNC prefixed path \\ (eg \\?\C:\foo) */
    120       max_sanitized_len = 32767-1;
    121     else
    122 #endif
    123       max_sanitized_len = PATH_MAX-1;
    124   }
    125   else
    126     /* The maximum length of a filename. FILENAME_MAX is often the same as
    127        PATH_MAX, in other words it is 260 and does not discount the path
    128        information therefore we should not use it. */
    129     max_sanitized_len = (PATH_MAX-1 > 255) ? 255 : PATH_MAX-1;
    130 
    131   len = strlen(file_name);
    132   if(len > max_sanitized_len)
    133     return SANITIZE_ERR_INVALID_PATH;
    134 
    135   target = strdup(file_name);
    136   if(!target)
    137     return SANITIZE_ERR_OUT_OF_MEMORY;
    138 
    139 #ifndef MSDOS
    140   if((flags & SANITIZE_ALLOW_PATH) && !strncmp(target, "\\\\?\\", 4))
    141     /* Skip the literal path prefix \\?\ */
    142     p = target + 4;
    143   else
    144 #endif
    145     p = target;
    146 
    147   /* replace control characters and other banned characters */
    148   for(; *p; ++p) {
    149     const char *banned;
    150 
    151     if((1 <= *p && *p <= 31) ||
    152        (!(flags & SANITIZE_ALLOW_PATH) && *p == ':') ||
    153        (!(flags & SANITIZE_ALLOW_PATH) && (*p == '/' || *p == '\\'))) {
    154       *p = '_';
    155       continue;
    156     }
    157 
    158     for(banned = "|<>\"?*"; *banned; ++banned) {
    159       if(*p == *banned) {
    160         *p = '_';
    161         break;
    162       }
    163     }
    164   }
    165 
    166   /* remove trailing spaces and periods if not allowing paths */
    167   if(!(flags & SANITIZE_ALLOW_PATH) && len) {
    168     char *clip = NULL;
    169 
    170     p = &target[len];
    171     do {
    172       --p;
    173       if(*p != ' ' && *p != '.')
    174         break;
    175       clip = p;
    176     } while(p != target);
    177 
    178     if(clip) {
    179       *clip = '\0';
    180     }
    181   }
    182 
    183 #ifdef MSDOS
    184   sc = msdosify(&p, target, flags);
    185   free(target);
    186   if(sc)
    187     return sc;
    188   target = p;
    189   len = strlen(target);
    190 
    191   if(len > max_sanitized_len) {
    192     free(target);
    193     return SANITIZE_ERR_INVALID_PATH;
    194   }
    195 #endif
    196 
    197   if(!(flags & SANITIZE_ALLOW_RESERVED)) {
    198     sc = rename_if_reserved_dos(&p, target, flags);
    199     free(target);
    200     if(sc)
    201       return sc;
    202     target = p;
    203     len = strlen(target);
    204 
    205     if(len > max_sanitized_len) {
    206       free(target);
    207       return SANITIZE_ERR_INVALID_PATH;
    208     }
    209   }
    210 
    211   *sanitized = target;
    212   return SANITIZE_ERR_OK;
    213 }
    214 
    215 #ifdef MSDOS
    216 /*
    217 Test if truncating a path to a file will leave at least a single character in
    218 the filename. Filenames suffixed by an alternate data stream cannot be
    219 truncated. This performs a dry run, nothing is modified.
    220 
    221 Good truncate_pos 9:    C:\foo\bar  =>  C:\foo\ba
    222 Good truncate_pos 6:    C:\foo      =>  C:\foo
    223 Good truncate_pos 5:    C:\foo      =>  C:\fo
    224 Bad* truncate_pos 5:    C:foo       =>  C:foo
    225 Bad truncate_pos 5:     C:\foo:ads  =>  C:\fo
    226 Bad truncate_pos 9:     C:\foo:ads  =>  C:\foo:ad
    227 Bad truncate_pos 5:     C:\foo\bar  =>  C:\fo
    228 Bad truncate_pos 5:     C:\foo\     =>  C:\fo
    229 Bad truncate_pos 7:     C:\foo\     =>  C:\foo\
    230 Error truncate_pos 7:   C:\foo      =>  (pos out of range)
    231 Bad truncate_pos 1:     C:\foo\     =>  C
    232 
    233 * C:foo is ambiguous, C could end up being a drive or file therefore something
    234   like C:superlongfilename cannot be truncated.
    235 
    236 Returns
    237 SANITIZE_ERR_OK: Good -- 'path' can be truncated
    238 SANITIZE_ERR_INVALID_PATH: Bad -- 'path' cannot be truncated
    239 != SANITIZE_ERR_OK && != SANITIZE_ERR_INVALID_PATH: Error
    240 */
    241 static SANITIZEcode truncate_dryrun(const char *path,
    242                                     const size_t truncate_pos)
    243 {
    244   size_t len;
    245 
    246   if(!path)
    247     return SANITIZE_ERR_BAD_ARGUMENT;
    248 
    249   len = strlen(path);
    250 
    251   if(truncate_pos > len)
    252     return SANITIZE_ERR_BAD_ARGUMENT;
    253 
    254   if(!len || !truncate_pos)
    255     return SANITIZE_ERR_INVALID_PATH;
    256 
    257   if(strpbrk(&path[truncate_pos - 1], "\\/:"))
    258     return SANITIZE_ERR_INVALID_PATH;
    259 
    260   /* C:\foo can be truncated but C:\foo:ads cannot */
    261   if(truncate_pos > 1) {
    262     const char *p = &path[truncate_pos - 1];
    263     do {
    264       --p;
    265       if(*p == ':')
    266         return SANITIZE_ERR_INVALID_PATH;
    267     } while(p != path && *p != '\\' && *p != '/');
    268   }
    269 
    270   return SANITIZE_ERR_OK;
    271 }
    272 
    273 /* The functions msdosify, rename_if_dos_device_name and __crt0_glob_function
    274  * were taken with modification from the DJGPP port of tar 1.12. They use
    275  * algorithms originally from DJTAR.
    276  */
    277 
    278 /*
    279 Extra sanitization MS-DOS for file_name.
    280 
    281 This is a supporting function for sanitize_file_name.
    282 
    283 Warning: This is an MS-DOS legacy function and was purposely written in a way
    284 that some path information may pass through. For example drive letter names
    285 (C:, D:, etc) are allowed to pass through. For sanitizing a filename use
    286 sanitize_file_name.
    287 
    288 Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
    289 Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
    290 */
    291 static SANITIZEcode msdosify(char **const sanitized, const char *file_name,
    292                              int flags)
    293 {
    294   char dos_name[PATH_MAX];
    295   static const char illegal_chars_dos[] = ".+, ;=[]" /* illegal in DOS */
    296     "|<>/\\\":?*"; /* illegal in DOS & W95 */
    297   static const char *illegal_chars_w95 = &illegal_chars_dos[8];
    298   int idx, dot_idx;
    299   const char *s = file_name;
    300   char *d = dos_name;
    301   const char *const dlimit = dos_name + sizeof(dos_name) - 1;
    302   const char *illegal_aliens = illegal_chars_dos;
    303   size_t len = sizeof(illegal_chars_dos) - 1;
    304 
    305   if(!sanitized)
    306     return SANITIZE_ERR_BAD_ARGUMENT;
    307 
    308   *sanitized = NULL;
    309 
    310   if(!file_name)
    311     return SANITIZE_ERR_BAD_ARGUMENT;
    312 
    313   if(strlen(file_name) > PATH_MAX-1)
    314     return SANITIZE_ERR_INVALID_PATH;
    315 
    316   /* Support for Windows 9X VFAT systems, when available. */
    317   if(_use_lfn(file_name)) {
    318     illegal_aliens = illegal_chars_w95;
    319     len -= (illegal_chars_w95 - illegal_chars_dos);
    320   }
    321 
    322   /* Get past the drive letter, if any. */
    323   if(s[0] >= 'A' && s[0] <= 'z' && s[1] == ':') {
    324     *d++ = *s++;
    325     *d = ((flags & SANITIZE_ALLOW_PATH)) ? ':' : '_';
    326     ++d; ++s;
    327   }
    328 
    329   for(idx = 0, dot_idx = -1; *s && d < dlimit; s++, d++) {
    330     if(memchr(illegal_aliens, *s, len)) {
    331 
    332       if((flags & SANITIZE_ALLOW_PATH) && *s == ':')
    333         *d = ':';
    334       else if((flags & SANITIZE_ALLOW_PATH) && (*s == '/' || *s == '\\'))
    335         *d = *s;
    336       /* Dots are special: DOS does not allow them as the leading character,
    337          and a filename cannot have more than a single dot. We leave the
    338          first non-leading dot alone, unless it comes too close to the
    339          beginning of the name: we want sh.lex.c to become sh_lex.c, not
    340          sh.lex-c.  */
    341       else if(*s == '.') {
    342         if((flags & SANITIZE_ALLOW_PATH) && idx == 0 &&
    343            (s[1] == '/' || s[1] == '\\' ||
    344             (s[1] == '.' && (s[2] == '/' || s[2] == '\\')))) {
    345           /* Copy "./" and "../" verbatim.  */
    346           *d++ = *s++;
    347           if(d == dlimit)
    348             break;
    349           if(*s == '.') {
    350             *d++ = *s++;
    351             if(d == dlimit)
    352               break;
    353           }
    354           *d = *s;
    355         }
    356         else if(idx == 0)
    357           *d = '_';
    358         else if(dot_idx >= 0) {
    359           if(dot_idx < 5) { /* 5 is a heuristic ad-hoc'ery */
    360             d[dot_idx - idx] = '_'; /* replace previous dot */
    361             *d = '.';
    362           }
    363           else
    364             *d = '-';
    365         }
    366         else
    367           *d = '.';
    368 
    369         if(*s == '.')
    370           dot_idx = idx;
    371       }
    372       else if(*s == '+' && s[1] == '+') {
    373         if(idx - 2 == dot_idx) { /* .c++, .h++ etc. */
    374           *d++ = 'x';
    375           if(d == dlimit)
    376             break;
    377           *d   = 'x';
    378         }
    379         else {
    380           /* libg++ etc.  */
    381           if(dlimit - d < 4) {
    382             *d++ = 'x';
    383             if(d == dlimit)
    384               break;
    385             *d   = 'x';
    386           }
    387           else {
    388             memcpy(d, "plus", 4);
    389             d += 3;
    390           }
    391         }
    392         s++;
    393         idx++;
    394       }
    395       else
    396         *d = '_';
    397     }
    398     else
    399       *d = *s;
    400     if(*s == '/' || *s == '\\') {
    401       idx = 0;
    402       dot_idx = -1;
    403     }
    404     else
    405       idx++;
    406   }
    407   *d = '\0';
    408 
    409   if(*s) {
    410     /* dos_name is truncated, check that truncation requirements are met,
    411        specifically truncating a filename suffixed by an alternate data stream
    412        or truncating the entire filename is not allowed. */
    413     if(strpbrk(s, "\\/:") || truncate_dryrun(dos_name, d - dos_name))
    414       return SANITIZE_ERR_INVALID_PATH;
    415   }
    416 
    417   *sanitized = strdup(dos_name);
    418   return *sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY;
    419 }
    420 #endif /* MSDOS */
    421 
    422 /*
    423 Rename file_name if it is a reserved dos device name.
    424 
    425 This is a supporting function for sanitize_file_name.
    426 
    427 Warning: This is an MS-DOS legacy function and was purposely written in a way
    428 that some path information may pass through. For example drive letter names
    429 (C:, D:, etc) are allowed to pass through. For sanitizing a filename use
    430 sanitize_file_name.
    431 
    432 Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
    433 Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
    434 */
    435 static SANITIZEcode rename_if_reserved_dos(char **const sanitized,
    436                                            const char *file_name,
    437                                            int flags)
    438 {
    439   /* We could have a file whose name is a device on MS-DOS. Trying to
    440    * retrieve such a file would fail at best and wedge us at worst. We need
    441    * to rename such files. */
    442   char *p, *base;
    443   char fname[PATH_MAX];
    444 #ifdef MSDOS
    445   struct_stat st_buf;
    446 #endif
    447   size_t len;
    448 
    449   if(!sanitized || !file_name)
    450     return SANITIZE_ERR_BAD_ARGUMENT;
    451 
    452   *sanitized = NULL;
    453   len = strlen(file_name);
    454 
    455   /* Ignore UNC prefixed paths, they are allowed to contain a reserved name. */
    456 #ifndef MSDOS
    457   if((flags & SANITIZE_ALLOW_PATH) &&
    458      file_name[0] == '\\' && file_name[1] == '\\') {
    459     *sanitized = strdup(file_name);
    460     if(!*sanitized)
    461       return SANITIZE_ERR_OUT_OF_MEMORY;
    462     return SANITIZE_ERR_OK;
    463   }
    464 #endif
    465 
    466   if(len > PATH_MAX-1)
    467     return SANITIZE_ERR_INVALID_PATH;
    468 
    469   memcpy(fname, file_name, len);
    470   fname[len] = '\0';
    471   base = basename(fname);
    472 
    473   /* Rename reserved device names that are known to be accessible without \\.\
    474      Examples: CON => _CON, CON.EXT => CON_EXT, CON:ADS => CON_ADS
    475      https://support.microsoft.com/en-us/kb/74496
    476      https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
    477      */
    478   for(p = fname; p; p = (p == fname && fname != base ? base : NULL)) {
    479     size_t p_len;
    480     int x = (curl_strnequal(p, "CON", 3) ||
    481              curl_strnequal(p, "PRN", 3) ||
    482              curl_strnequal(p, "AUX", 3) ||
    483              curl_strnequal(p, "NUL", 3)) ? 3 :
    484             (curl_strnequal(p, "CLOCK$", 6)) ? 6 :
    485             (curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ?
    486               (('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0;
    487 
    488     if(!x)
    489       continue;
    490 
    491     /* the devices may be accessible with an extension or ADS, for
    492        example CON.AIR and 'CON . AIR' and CON:AIR access console */
    493 
    494     for(; p[x] == ' '; ++x)
    495       ;
    496 
    497     if(p[x] == '.') {
    498       p[x] = '_';
    499       continue;
    500     }
    501     else if(p[x] == ':') {
    502       if(!(flags & SANITIZE_ALLOW_PATH)) {
    503         p[x] = '_';
    504         continue;
    505       }
    506       ++x;
    507     }
    508     else if(p[x]) /* no match */
    509       continue;
    510 
    511     /* p points to 'CON' or 'CON ' or 'CON:', etc */
    512     p_len = strlen(p);
    513 
    514     /* Prepend a '_' */
    515     if(strlen(fname) == PATH_MAX-1)
    516       return SANITIZE_ERR_INVALID_PATH;
    517     memmove(p + 1, p, p_len + 1);
    518     p[0] = '_';
    519     ++p_len;
    520 
    521     /* if fname was just modified then the basename pointer must be updated */
    522     if(p == fname)
    523       base = basename(fname);
    524   }
    525 
    526   /* This is the legacy portion from rename_if_dos_device_name that checks for
    527      reserved device names. It only works on MS-DOS. On Windows XP the stat
    528      check errors with EINVAL if the device name is reserved. On Windows
    529      Vista/7/8 it sets mode S_IFREG (regular file or device). According to
    530      MSDN stat doc the latter behavior is correct, but that does not help us
    531      identify whether it is a reserved device name and not a regular
    532      filename. */
    533 #ifdef MSDOS
    534   if(base && ((stat(base, &st_buf)) == 0) && (S_ISCHR(st_buf.st_mode))) {
    535     /* Prepend a '_' */
    536     size_t blen = strlen(base);
    537     if(blen) {
    538       if(strlen(fname) >= PATH_MAX-1)
    539         return SANITIZE_ERR_INVALID_PATH;
    540       memmove(base + 1, base, blen + 1);
    541       base[0] = '_';
    542     }
    543   }
    544 #endif
    545 
    546   *sanitized = strdup(fname);
    547   return *sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY;
    548 }
    549 
    550 #ifdef __DJGPP__
    551 /*
    552  * Disable program default argument globbing. We do it on our own.
    553  */
    554 char **__crt0_glob_function(char *arg)
    555 {
    556   (void)arg;
    557   return (char **)0;
    558 }
    559 #endif
    560 
    561 #ifdef _WIN32
    562 
    563 #if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE) && \
    564   !defined(CURL_DISABLE_CA_SEARCH) && !defined(CURL_CA_SEARCH_SAFE)
    565 /* Search and set the CA cert file for Windows.
    566  *
    567  * Do not call this function if Schannel is the selected SSL backend. We allow
    568  * setting CA location for Schannel only when explicitly specified by the user
    569  * via CURLOPT_CAINFO / --cacert.
    570  *
    571  * Function to find CACert bundle on a Win32 platform using SearchPath.
    572  * (SearchPath is already declared via inclusions done in setup header file)
    573  * (Use the ASCII version instead of the Unicode one!)
    574  * The order of the directories it searches is:
    575  *  1. application's directory
    576  *  2. current working directory
    577  *  3. Windows System directory (e.g. C:\Windows\System32)
    578  *  4. Windows Directory (e.g. C:\Windows)
    579  *  5. all directories along %PATH%
    580  *
    581  * For WinXP and later search order actually depends on registry value:
    582  * HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeProcessSearchMode
    583  */
    584 CURLcode FindWin32CACert(struct OperationConfig *config,
    585                          const TCHAR *bundle_file)
    586 {
    587   CURLcode result = CURLE_OK;
    588   DWORD res_len;
    589   TCHAR buf[PATH_MAX];
    590   TCHAR *ptr = NULL;
    591 
    592   buf[0] = TEXT('\0');
    593 
    594   res_len = SearchPath(NULL, bundle_file, NULL, PATH_MAX, buf, &ptr);
    595   if(res_len > 0) {
    596     char *mstr = curlx_convert_tchar_to_UTF8(buf);
    597     tool_safefree(config->cacert);
    598     if(mstr)
    599       config->cacert = strdup(mstr);
    600     curlx_unicodefree(mstr);
    601     if(!config->cacert)
    602       result = CURLE_OUT_OF_MEMORY;
    603   }
    604 
    605   return result;
    606 }
    607 #endif
    608 
    609 /* Get a list of all loaded modules with full paths.
    610  * Returns slist on success or NULL on error.
    611  */
    612 struct curl_slist *GetLoadedModulePaths(void)
    613 {
    614   struct curl_slist *slist = NULL;
    615 #if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
    616   HANDLE hnd = INVALID_HANDLE_VALUE;
    617   MODULEENTRY32 mod = {0};
    618 
    619   mod.dwSize = sizeof(MODULEENTRY32);
    620 
    621   do {
    622     hnd = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
    623   } while(hnd == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
    624 
    625   if(hnd == INVALID_HANDLE_VALUE)
    626     goto error;
    627 
    628   if(!Module32First(hnd, &mod))
    629     goto error;
    630 
    631   do {
    632     char *path; /* points to stack allocated buffer */
    633     struct curl_slist *temp;
    634 
    635 #ifdef UNICODE
    636     /* sizeof(mod.szExePath) is the max total bytes of wchars. the max total
    637        bytes of multibyte chars will not be more than twice that. */
    638     char buffer[sizeof(mod.szExePath) * 2];
    639     if(!WideCharToMultiByte(CP_ACP, 0, mod.szExePath, -1,
    640                             buffer, sizeof(buffer), NULL, NULL))
    641       goto error;
    642     path = buffer;
    643 #else
    644     path = mod.szExePath;
    645 #endif
    646     temp = curl_slist_append(slist, path);
    647     if(!temp)
    648       goto error;
    649     slist = temp;
    650   } while(Module32Next(hnd, &mod));
    651 
    652   goto cleanup;
    653 
    654 error:
    655   curl_slist_free_all(slist);
    656   slist = NULL;
    657 cleanup:
    658   if(hnd != INVALID_HANDLE_VALUE)
    659     CloseHandle(hnd);
    660 #endif
    661   return slist;
    662 }
    663 
    664 bool tool_term_has_bold;
    665 
    666 #if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
    667 /* The terminal settings to restore on exit */
    668 static struct TerminalSettings {
    669   HANDLE hStdOut;
    670   DWORD dwOutputMode;
    671   LONG valid;
    672 } TerminalSettings;
    673 
    674 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
    675 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
    676 #endif
    677 
    678 static void restore_terminal(void)
    679 {
    680   if(InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE))
    681     SetConsoleMode(TerminalSettings.hStdOut, TerminalSettings.dwOutputMode);
    682 }
    683 
    684 /* This is the console signal handler.
    685  * The system calls it in a separate thread.
    686  */
    687 static BOOL WINAPI signal_handler(DWORD type)
    688 {
    689   if(type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT)
    690     restore_terminal();
    691   return FALSE;
    692 }
    693 
    694 static void init_terminal(void)
    695 {
    696   TerminalSettings.hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    697 
    698   /*
    699    * Enable VT (Virtual Terminal) output.
    700    * Note: VT mode flag can be set on any version of Windows, but VT
    701    * processing only performed on Win10 >= version 1709 (OS build 16299)
    702    * Creator's Update. Also, ANSI bold on/off supported since then.
    703    */
    704   if(TerminalSettings.hStdOut == INVALID_HANDLE_VALUE ||
    705      !GetConsoleMode(TerminalSettings.hStdOut,
    706                      &TerminalSettings.dwOutputMode) ||
    707      !curlx_verify_windows_version(10, 0, 16299, PLATFORM_WINNT,
    708                                    VERSION_GREATER_THAN_EQUAL))
    709     return;
    710 
    711   if((TerminalSettings.dwOutputMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))
    712     tool_term_has_bold = true;
    713   else {
    714     /* The signal handler is set before attempting to change the console mode
    715        because otherwise a signal would not be caught after the change but
    716        before the handler was installed. */
    717     (void)InterlockedExchange(&TerminalSettings.valid, (LONG)TRUE);
    718     if(SetConsoleCtrlHandler(signal_handler, TRUE)) {
    719       if(SetConsoleMode(TerminalSettings.hStdOut,
    720                         (TerminalSettings.dwOutputMode |
    721                          ENABLE_VIRTUAL_TERMINAL_PROCESSING))) {
    722         tool_term_has_bold = true;
    723         atexit(restore_terminal);
    724       }
    725       else {
    726         SetConsoleCtrlHandler(signal_handler, FALSE);
    727         (void)InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE);
    728       }
    729     }
    730   }
    731 }
    732 #endif
    733 
    734 CURLcode win32_init(void)
    735 {
    736   curlx_now_init();
    737 #if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
    738   init_terminal();
    739 #endif
    740 
    741   return CURLE_OK;
    742 }
    743 
    744 #if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
    745 /* The following STDIN non - blocking read techniques are heavily inspired
    746    by nmap and ncat (https://nmap.org/ncat/) */
    747 struct win_thread_data {
    748   /* This is a copy of the true stdin file handle before any redirection. It is
    749       read by the thread. */
    750   HANDLE stdin_handle;
    751   /* This is the listen socket for the thread. It is closed after the first
    752       connection. */
    753   curl_socket_t socket_l;
    754   /* This is the global config - used for printing errors and so forth */
    755   struct GlobalConfig *global;
    756 };
    757 
    758 static DWORD WINAPI win_stdin_thread_func(void *thread_data)
    759 {
    760   struct win_thread_data *tdata = (struct win_thread_data *)thread_data;
    761   DWORD n;
    762   int nwritten;
    763   char buffer[BUFSIZ];
    764   BOOL r;
    765 
    766   SOCKADDR_IN clientAddr;
    767   int clientAddrLen = sizeof(clientAddr);
    768 
    769   curl_socket_t socket_w = accept(tdata->socket_l, (SOCKADDR*)&clientAddr,
    770                            &clientAddrLen);
    771 
    772   if(socket_w == CURL_SOCKET_BAD) {
    773     errorf(tdata->global, "accept error: %08lx\n", GetLastError());
    774     goto ThreadCleanup;
    775   }
    776 
    777   closesocket(tdata->socket_l); /* sclose here fails test 1498 */
    778   tdata->socket_l = CURL_SOCKET_BAD;
    779   if(shutdown(socket_w, SD_RECEIVE) == SOCKET_ERROR) {
    780     errorf(tdata->global, "shutdown error: %08lx\n", GetLastError());
    781     goto ThreadCleanup;
    782   }
    783   for(;;) {
    784     r = ReadFile(tdata->stdin_handle, buffer, sizeof(buffer), &n, NULL);
    785     if(r == 0)
    786       break;
    787     if(n == 0)
    788       break;
    789     nwritten = send(socket_w, buffer, n, 0);
    790     if(nwritten == SOCKET_ERROR)
    791       break;
    792     if((DWORD)nwritten != n)
    793       break;
    794   }
    795 ThreadCleanup:
    796   CloseHandle(tdata->stdin_handle);
    797   tdata->stdin_handle = NULL;
    798   if(tdata->socket_l != CURL_SOCKET_BAD) {
    799     sclose(tdata->socket_l);
    800     tdata->socket_l = CURL_SOCKET_BAD;
    801   }
    802   if(socket_w != CURL_SOCKET_BAD)
    803     sclose(socket_w);
    804 
    805   if(tdata) {
    806     free(tdata);
    807   }
    808 
    809   return 0;
    810 }
    811 
    812 /* The background thread that reads and buffers the true stdin. */
    813 static HANDLE stdin_thread = NULL;
    814 static curl_socket_t socket_r = CURL_SOCKET_BAD;
    815 
    816 curl_socket_t win32_stdin_read_thread(struct GlobalConfig *global)
    817 {
    818   int result;
    819   bool r;
    820   int rc = 0, socksize = 0;
    821   struct win_thread_data *tdata = NULL;
    822   SOCKADDR_IN selfaddr;
    823 
    824   if(socket_r != CURL_SOCKET_BAD) {
    825     assert(stdin_thread != NULL);
    826     return socket_r;
    827   }
    828   assert(stdin_thread == NULL);
    829 
    830   do {
    831     /* Prepare handles for thread */
    832     tdata = (struct win_thread_data*)calloc(1, sizeof(struct win_thread_data));
    833     if(!tdata) {
    834       errorf(global, "calloc() error");
    835       break;
    836     }
    837     /* Create the listening socket for the thread. When it starts, it will
    838     * accept our connection and begin writing STDIN data to the connection. */
    839     tdata->socket_l = WSASocketW(AF_INET, SOCK_STREAM,
    840                                  IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    841 
    842     if(tdata->socket_l == CURL_SOCKET_BAD) {
    843       errorf(global, "WSASocketW error: %08lx", GetLastError());
    844       break;
    845     }
    846 
    847     socksize = sizeof(selfaddr);
    848     memset(&selfaddr, 0, socksize);
    849     selfaddr.sin_family = AF_INET;
    850     selfaddr.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK);
    851     /* Bind to any available loopback port */
    852     result = bind(tdata->socket_l, (SOCKADDR*)&selfaddr, socksize);
    853     if(result == SOCKET_ERROR) {
    854       errorf(global, "bind error: %08lx", GetLastError());
    855       break;
    856     }
    857 
    858     /* Bind to any available loopback port */
    859     result = getsockname(tdata->socket_l, (SOCKADDR*)&selfaddr, &socksize);
    860     if(result == SOCKET_ERROR) {
    861       errorf(global, "getsockname error: %08lx", GetLastError());
    862       break;
    863     }
    864 
    865     result = listen(tdata->socket_l, 1);
    866     if(result == SOCKET_ERROR) {
    867       errorf(global, "listen error: %08lx\n", GetLastError());
    868       break;
    869     }
    870 
    871     /* Make a copy of the stdin handle to be used by win_stdin_thread_func */
    872     r = DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE),
    873                         GetCurrentProcess(), &tdata->stdin_handle,
    874                         0, FALSE, DUPLICATE_SAME_ACCESS);
    875 
    876     if(!r) {
    877       errorf(global, "DuplicateHandle error: %08lx", GetLastError());
    878       break;
    879     }
    880 
    881     /* Start up the thread. We don't bother keeping a reference to it
    882        because it runs until program termination. From here on out all reads
    883        from the stdin handle or file descriptor 0 will be reading from the
    884        socket that is fed by the thread. */
    885     stdin_thread = CreateThread(NULL, 0, win_stdin_thread_func,
    886                                 tdata, 0, NULL);
    887     if(!stdin_thread) {
    888       errorf(global, "CreateThread error: %08lx", GetLastError());
    889       break;
    890     }
    891 
    892     /* Connect to the thread and rearrange our own STDIN handles */
    893     socket_r = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    894     if(socket_r == CURL_SOCKET_BAD) {
    895       errorf(global, "socket error: %08lx", GetLastError());
    896       break;
    897     }
    898 
    899     /* Hard close the socket on closesocket() */
    900     setsockopt(socket_r, SOL_SOCKET, SO_DONTLINGER, 0, 0);
    901 
    902     if(connect(socket_r, (SOCKADDR*)&selfaddr, socksize) == SOCKET_ERROR) {
    903       errorf(global, "connect error: %08lx", GetLastError());
    904       break;
    905     }
    906 
    907     if(shutdown(socket_r, SD_SEND) == SOCKET_ERROR) {
    908       errorf(global, "shutdown error: %08lx", GetLastError());
    909       break;
    910     }
    911 
    912     /* Set the stdin handle to read from the socket. */
    913     if(SetStdHandle(STD_INPUT_HANDLE, (HANDLE)socket_r) == 0) {
    914       errorf(global, "SetStdHandle error: %08lx", GetLastError());
    915       break;
    916     }
    917 
    918     rc = 1;
    919   } while(0);
    920 
    921   if(rc != 1) {
    922     if(socket_r != CURL_SOCKET_BAD && tdata) {
    923       if(GetStdHandle(STD_INPUT_HANDLE) == (HANDLE)socket_r &&
    924         tdata->stdin_handle) {
    925           /* restore STDIN */
    926           SetStdHandle(STD_INPUT_HANDLE, tdata->stdin_handle);
    927           tdata->stdin_handle = NULL;
    928         }
    929 
    930       sclose(socket_r);
    931       socket_r = CURL_SOCKET_BAD;
    932     }
    933 
    934     if(stdin_thread) {
    935       TerminateThread(stdin_thread, 1);
    936       stdin_thread = NULL;
    937     }
    938 
    939     if(tdata) {
    940       if(tdata->stdin_handle)
    941         CloseHandle(tdata->stdin_handle);
    942       if(tdata->socket_l != CURL_SOCKET_BAD)
    943         sclose(tdata->socket_l);
    944 
    945       free(tdata);
    946     }
    947 
    948     return CURL_SOCKET_BAD;
    949   }
    950 
    951   assert(socket_r != CURL_SOCKET_BAD);
    952   return socket_r;
    953 }
    954 
    955 #endif /* !CURL_WINDOWS_UWP && !UNDER_CE */
    956 
    957 #endif /* _WIN32 */
    958 
    959 #endif /* _WIN32 || MSDOS */