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