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