Imported Upstream version 7.53.1
[platform/upstream/curl.git] / src / tool_doswin.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2016, 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.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     DWORD buf_tchar_size = PATH_MAX + 1;
650     DWORD buf_bytes_size = sizeof(TCHAR) * buf_tchar_size;
651     char *ptr = NULL;
652
653     char *buf = malloc(buf_bytes_size);
654     if(!buf)
655       return CURLE_OUT_OF_MEMORY;
656     buf[0] = '\0';
657
658     res_len = SearchPathA(NULL, bundle_file, NULL, buf_tchar_size, buf, &ptr);
659     if(res_len > 0) {
660       Curl_safefree(config->cacert);
661       config->cacert = strdup(buf);
662       if(!config->cacert)
663         result = CURLE_OUT_OF_MEMORY;
664     }
665
666     Curl_safefree(buf);
667   }
668
669   return result;
670 }
671
672 #endif /* WIN32 */
673
674 #endif /* MSDOS || WIN32 */