]> granicus.if.org Git - apache/blob - os/win32/util_win32.c
Win32: Get mod_auth_digest compiling and added to the Windows
[apache] / os / win32 / util_win32.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 #include <windows.h>
60 #include <sys/stat.h>
61 #include <stdarg.h>
62 #include <time.h>
63 #include <stdlib.h>
64
65 #include "httpd.h"
66 #include "http_log.h"
67
68 /* Returns TRUE if the input string is a string
69  * of one or more '.' characters.
70  */
71 static BOOL OnlyDots(char *pString)
72 {
73     char *c;
74
75     if (*pString == '\0')
76         return FALSE;
77
78     for (c = pString;*c;c++)
79         if (*c != '.')
80             return FALSE;
81
82     return TRUE;
83 }
84
85 /* Accepts as input a pathname, and tries to match it to an 
86  * existing path and return the pathname in the case that
87  * is present on the existing path.  This routine also
88  * converts alias names to long names.
89  */
90 API_EXPORT(char *) ap_os_systemcase_filename(ap_pool_t *pPool, 
91                                              const char *szFile)
92 {
93     char buf[HUGE_STRING_LEN];
94     char *pInputName;
95     char *p, *q;
96     BOOL bDone = FALSE;
97     BOOL bFileExists = TRUE;
98     HANDLE hFind;
99     WIN32_FIND_DATA wfd;
100
101     if (!szFile || strlen(szFile) == 0 || strlen(szFile) >= sizeof(buf))
102         return ap_pstrdup(pPool, "");
103
104     buf[0] = '\0';
105     pInputName = ap_pstrdup(pPool, szFile);
106
107     /* First convert all slashes to \ so Win32 calls work OK */
108     for (p = pInputName; *p; p++) {
109         if (*p == '/')
110             *p = '\\';
111     }
112     
113     p = pInputName;
114     /* If there is drive information, copy it over. */ 
115     if (pInputName[1] == ':') {
116         buf[0] = tolower(*p++);
117         buf[1] = *p++;
118         buf[2] = '\0';
119
120         /* If all we have is a drive letter, then we are done */
121         if (strlen(pInputName) == 2)
122             bDone = TRUE;
123     }
124     
125     q = p;
126     if (*p == '\\') {
127         p++;
128         if (*p == '\\')  /* Possible UNC name */
129         {
130             p++;
131             /* Get past the machine name.  FindFirstFile */
132             /* will not find a machine name only */
133             p = strchr(p, '\\'); 
134             if (p)
135             {
136                 p++;
137                 /* Get past the share name.  FindFirstFile */
138                 /* will not find a \\machine\share name only */
139                 p = strchr(p, '\\'); 
140                 if (p) {
141                     strncat(buf,q,p-q);
142                     q = p;
143                     p++;
144                 }
145             }
146
147             if (!p)
148                 p = q;
149         }
150     }
151
152     p = strchr(p, '\\');
153
154     while (!bDone) {
155         if (p)
156             *p = '\0';
157
158         if (strchr(q, '*') || strchr(q, '?'))
159             bFileExists = FALSE;
160
161         /* If the path exists so far, call FindFirstFile
162          * again.  However, if this portion of the path contains
163          * only '.' charaters, skip the call to FindFirstFile
164          * since it will convert '.' and '..' to actual names.
165          * Note: in the call to OnlyDots, we may have to skip
166          *       a leading slash.
167          */
168         if (bFileExists && !OnlyDots((*q == '.' ? q : q+1))) {            
169             hFind = FindFirstFile(pInputName, &wfd);
170             
171             if (hFind == INVALID_HANDLE_VALUE) {
172                 bFileExists = FALSE;
173             }
174             else {
175                 FindClose(hFind);
176
177                 if (*q == '\\')
178                     strcat(buf,"\\");
179                 strcat(buf, wfd.cFileName);
180             }
181         }
182         
183         if (!bFileExists || OnlyDots((*q == '.' ? q : q+1))) {
184             strcat(buf, q);
185         }
186         
187         if (p) {
188             q = p;
189             *p++ = '\\';
190             p = strchr(p, '\\');
191         }
192         else {
193             bDone = TRUE;
194         }
195     }
196     
197     /* First convert all slashes to / so server code handles it ok */
198     for (p = buf; *p; p++) {
199         if (*p == '\\')
200             *p = '/';
201     }
202
203     return ap_pstrdup(pPool, buf);
204 }
205
206
207 /*  Perform canonicalization with the exception that the
208  *  input case is preserved.
209  */
210 API_EXPORT(char *) ap_os_case_canonical_filename(ap_pool_t *pPool, 
211                                                  const char *szFile)
212 {
213     char *pNewStr;
214     char *s;
215     char *p; 
216     char *q;
217
218     if (szFile == NULL || strlen(szFile) == 0)
219         return ap_pstrdup(pPool, "");
220
221     pNewStr = ap_pstrdup(pPool, szFile);
222
223     /*  Change all '\' characters to '/' characters.
224      *  While doing this, remove any trailing '.'.
225      *  Also, blow away any directories with 3 or
226      *  more '.'
227      */
228     for (p = pNewStr,s = pNewStr; *s; s++,p++) {
229         if (*s == '\\' || *s == '/') {
230
231             q = p;
232             while (p > pNewStr && *(p-1) == '.')
233                 p--;
234
235             if (p == pNewStr && q-p <= 2 && *p == '.')
236                 p = q;
237             else if (p > pNewStr && p < q && *(p-1) == '/') {
238                 if (q-p > 2)
239                     p--;
240                 else
241                     p = q;
242             }
243
244             *p = '/';
245         }
246         else {
247             *p = *s;
248         }
249     }
250     *p = '\0';
251
252     /*  Blow away any final trailing '.' since on Win32
253      *  foo.bat == foo.bat. == foo.bat... etc.
254      *  Also blow away any trailing spaces since
255      *  "filename" == "filename "
256      */
257     q = p;
258     while (p > pNewStr && (*(p-1) == '.' || *(p-1) == ' '))
259         p--;
260     if ((p > pNewStr) ||
261         (p == pNewStr && q-p > 2))
262         *p = '\0';
263         
264
265     /*  One more security issue to deal with.  Win32 allows
266      *  you to create long filenames.  However, alias filenames
267      *  are always created so that the filename will
268      *  conform to 8.3 rules.  According to the Microsoft
269      *  Developer's network CD (1/98) 
270      *  "Automatically generated aliases are composed of the 
271      *   first six characters of the filename plus ~n 
272      *   (where n is a number) and the first three characters 
273      *   after the last period."
274      *  Here, we attempt to detect and decode these names.
275      */
276     p = strchr(pNewStr, '~');
277     if (p != NULL) {
278         char *pConvertedName, *pQstr, *pPstr;
279         char buf[HUGE_STRING_LEN];
280         /* We potentially have a short name.  Call 
281          * ap_os_systemcase_filename to examine the filesystem
282          * and possibly extract the long name.
283          */
284         pConvertedName = ap_os_systemcase_filename(pPool, pNewStr);
285
286         /* Since we want to preserve the incoming case as much
287          * as we can, compare for differences in the string and
288          * only substitute in the path names that changed.
289          */
290         if (stricmp(pNewStr, pConvertedName)) {
291             buf[0] = '\0';
292
293             q = pQstr = pConvertedName;
294             p = pPstr = pNewStr;
295             do {
296                 q = strchr(q,'/');
297                 p = strchr(p,'/');
298
299                 if (p != NULL) {
300                     *q = '\0';
301                     *p = '\0';
302                 }
303
304                 if (stricmp(pQstr, pPstr)) 
305                     strcat(buf, pQstr);   /* Converted name */
306                 else 
307                     strcat(buf, pPstr);   /* Original name  */
308
309
310                 if (p != NULL) {
311                     pQstr = q;
312                     pPstr = p;
313                     *q++ = '/';
314                     *p++ = '/';
315                 }
316
317             } while (p != NULL); 
318
319             pNewStr = ap_pstrdup(pPool, buf);
320         }
321     }
322
323
324     return pNewStr;
325 }
326
327 /*  Perform complete canonicalization.
328  */
329 API_EXPORT(char *) ap_os_canonical_filename(ap_pool_t *pPool, const char *szFile)
330 {
331     char *pNewName;
332     pNewName = ap_os_case_canonical_filename(pPool, szFile);
333     strlwr(pNewName);
334     return pNewName;
335 }
336
337 /* Win95 doesn't like trailing /s. NT and Unix don't mind. This works 
338  * around the problem.
339  * Errr... except if it is UNC and we are referring to the root of 
340  * the UNC, we MUST have a trailing \ and we can't use /s. Jeez. 
341  * Not sure if this refers to all UNCs or just roots,
342  * but I'm going to fix it for all cases for now. (Ben)
343  */
344
345 #undef stat
346 API_EXPORT(int) os_stat(const char *szPath, struct stat *pStat)
347 {
348     int n;
349     
350     if (strlen(szPath) == 0) {
351         return -1;
352     }
353
354     if (szPath[0] == '/' && szPath[1] == '/') {
355         char buf[_MAX_PATH];
356         char *s;
357         int nSlashes = 0;
358
359         ap_assert(strlen(szPath) < _MAX_PATH);
360         strcpy(buf, szPath);
361         for (s = buf; *s; ++s) {
362             if (*s == '/') {
363                 *s = '\\';
364                 ++nSlashes;
365             }
366         }
367         /* then we need to add one more to get \\machine\share\ */
368         if (nSlashes == 3) {
369             *s++ = '\\';
370         }
371         *s = '\0';
372         return stat(buf, pStat);
373     }
374
375     /*
376      * Below removes the trailing /, however, do not remove
377      * it in the case of 'x:/' or stat will fail
378      */
379     n = strlen(szPath);
380     if ((szPath[n - 1] == '\\' || szPath[n - 1] == '/') &&
381         !(n == 3 && szPath[1] == ':')) {
382         char buf[_MAX_PATH];
383         
384         ap_assert(n < _MAX_PATH);
385         strcpy(buf, szPath);
386         buf[n - 1] = '\0';
387         
388         return stat(buf, pStat);
389     }
390     return stat(szPath, pStat);
391 }
392
393
394 #undef strftime
395
396 /* Partial replacement for strftime. This adds certain expandos to the
397  * Windows version
398  */
399
400 API_EXPORT(int) os_strftime(char *s, size_t max, const char *format,
401                             const struct tm *tm) {
402    /* If the new format string is bigger than max, the result string probably
403     * won't fit anyway. When %-expandos are added, made sure the padding below
404     * is enough.
405     */
406     char *new_format = (char *) _alloca(max + 11);
407     size_t i, j, format_length = strlen(format);
408     int return_value;
409     int length_written;
410
411     for (i = 0, j = 0; (i < format_length && j < max);) {
412         if (format[i] != '%') {
413             new_format[j++] = format[i++];
414             continue;
415         }
416         switch (format[i+1]) {
417             case 'D':
418                 /* Is this locale dependent? Shouldn't be...
419                    Also note the year 2000 exposure here */
420                 memcpy(new_format + j, "%m/%d/%y", 8);
421                 i += 2;
422                 j += 8;
423                 break;
424             case 'r':
425                 memcpy(new_format + j, "%I:%M:%S %p", 11);
426                 i += 2;
427                 j += 11;
428                 break;
429             case 'T':
430                 memcpy(new_format + j, "%H:%M:%S", 8);
431                 i += 2;
432                 j += 8;
433                 break;
434             case 'e':
435                 length_written = ap_snprintf(new_format + j, max - j, "%2d",
436                     tm->tm_mday);
437                 j = (length_written == -1) ? max : (j + length_written);
438                 i += 2;
439                 break;
440             default:
441                 /* We know we can advance two characters forward here. */
442                 new_format[j++] = format[i++];
443                 new_format[j++] = format[i++];
444         }
445     }
446     if (j >= max) {
447         *s = '\0';  /* Defensive programming, okay since output is undefined */
448         return_value = 0;
449     } else {
450         new_format[j] = '\0';
451         return_value = strftime(s, max, new_format, tm);
452     }
453     return return_value;
454 }
455
456 /*
457  * ap_os_is_filename_valid is given a filename, and returns 0 if the filename
458  * is not valid for use on this system. On Windows, this means it fails any
459  * of the tests below. Otherwise returns 1.
460  *
461  * Test for filename validity on Win32. This is of tests come in part from
462  * the MSDN article at "Technical Articles, Windows Platform, Base Services,
463  * Guidelines, Making Room for Long Filenames" although the information
464  * in MSDN about filename testing is incomplete or conflicting. There is a
465  * similar set of tests in "Technical Articles, Windows Platform, Base Services,
466  * Guidelines, Moving Unix Applications to Windows NT".
467  *
468  * The tests are:
469  *
470  * 1) total path length greater than MAX_PATH
471  *
472  * 2) anything using the octets 0-31 or characters " < > | :
473  *    (these are reserved for Windows use in filenames. In addition
474  *     each file system has its own additional characters that are
475  *     invalid. See KB article Q100108 for more details).
476  *
477  * 3) anything ending in "." (no matter how many)
478  *    (filename doc, doc. and doc... all refer to the same file)
479  *
480  * 4) any segment in which the basename (before first period) matches
481  *    one of the DOS device names
482  *    (the list comes from KB article Q100108 although some people
483  *     reports that additional names such as "COM5" are also special
484  *     devices).
485  *
486  * If the path fails ANY of these tests, the result must be to deny access.
487  */
488
489 API_EXPORT(int) ap_os_is_filename_valid(const char *file)
490 {
491     const char *segstart;
492     unsigned int seglength;
493     const char *pos;
494     static const char * const invalid_characters = "?\"<>*|:";
495     static const char * const invalid_filenames[] = { 
496         "CON", "AUX", "COM1", "COM2", "COM3", 
497         "COM4", "LPT1", "LPT2", "LPT3", "PRN", "NUL", NULL 
498     };
499
500     /* Test 1 */
501     if (strlen(file) > MAX_PATH) {
502         /* Path too long for Windows. Note that this test is not valid
503          * if the path starts with //?/ or \\?\. */
504         return 0;
505     }
506
507     pos = file;
508
509     /* Skip any leading non-path components. This can be either a
510      * drive letter such as C:, or a UNC path such as \\SERVER\SHARE\.
511      * We continue and check the rest of the path based on the rules above.
512      * This means we could eliminate valid filenames from servers which
513      * are not running NT (such as Samba).
514      */
515
516     if (pos[0] && pos[1] == ':') {
517         /* Skip leading drive letter */
518         pos += 2;
519     }
520     else {
521         if ((pos[0] == '\\' || pos[0] == '/') &&
522             (pos[1] == '\\' || pos[1] == '/')) {
523             /* Is a UNC, so skip the server name and share name */
524             pos += 2;
525             while (*pos && *pos != '/' && *pos != '\\')
526                 pos++;
527             if (!*pos) {
528                 /* No share name */
529                 return 0;
530             }
531             pos++;      /* Move to start of share name */
532             while (*pos && *pos != '/' && *pos != '\\')
533                 pos++;
534             if (!*pos) {
535                 /* No path information */
536                 return 0;
537             }
538         }
539     }
540
541     while (*pos) {
542         unsigned int idx;
543         unsigned int baselength;
544
545         while (*pos == '/' || *pos == '\\') {
546             pos++;
547         }
548         if (*pos == '\0') {
549             break;
550         }
551         segstart = pos; /* start of segment */
552         while (*pos && *pos != '/' && *pos != '\\') {
553             pos++;
554         }
555         seglength = pos - segstart;
556         /* 
557          * Now we have a segment of the path, starting at position "segstart"
558          * and length "seglength"
559          */
560
561         /* Test 2 */
562         for (idx = 0; idx < seglength; idx++) {
563             if ((segstart[idx] > 0 && segstart[idx] < 32) ||
564                 strchr(invalid_characters, segstart[idx])) {
565                 return 0;
566             }
567         }
568
569         /* Test 3 */
570         if (segstart[seglength-1] == '.') {
571             return 0;
572         }
573
574         /* Test 4 */
575         for (baselength = 0; baselength < seglength; baselength++) {
576             if (segstart[baselength] == '.') {
577                 break;
578             }
579         }
580
581         /* baselength is the number of characters in the base path of
582          * the segment (which could be the same as the whole segment length,
583          * if it does not include any dot characters). */
584         if (baselength == 3 || baselength == 4) {
585             for (idx = 0; invalid_filenames[idx]; idx++) {
586                 if (strlen(invalid_filenames[idx]) == baselength &&
587                     !strnicmp(invalid_filenames[idx], segstart, baselength)) {
588                     return 0;
589                 }
590             }
591         }
592     }
593
594     return 1;
595 }