]> granicus.if.org Git - apache/blob - os/win32/util_win32.c
Time for these to be gone? I'd say it's long past :)
[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 <stdarg.h>
63 #include <time.h>
64 #include <stdlib.h>
65
66 /* Returns TRUE if the input string is a string
67  * of one or more '.' characters.
68  */
69 static BOOL OnlyDots(char *pString)
70 {
71     char *c;
72
73     if (*pString == '\0')
74         return FALSE;
75
76     for (c = pString;*c;c++)
77         if (*c != '.')
78             return FALSE;
79
80     return TRUE;
81 }
82
83 /* Accepts as input a pathname, and tries to match it to an 
84  * existing path and return the pathname in the case that
85  * is present on the existing path.  This routine also
86  * converts alias names to long names.
87  */
88 API_EXPORT(char *) ap_os_systemcase_filename(ap_pool_t *pPool, 
89                                              const char *szFile)
90 {
91     char buf[HUGE_STRING_LEN];
92     char *pInputName;
93     char *p, *q;
94     BOOL bDone = FALSE;
95     BOOL bFileExists = TRUE;
96     HANDLE hFind;
97     WIN32_FIND_DATA wfd;
98
99     if (!szFile || strlen(szFile) == 0 || strlen(szFile) >= sizeof(buf))
100         return ap_pstrdup(pPool, "");
101
102     buf[0] = '\0';
103     pInputName = ap_pstrdup(pPool, szFile);
104
105     /* First convert all slashes to \ so Win32 calls work OK */
106     for (p = pInputName; *p; p++) {
107         if (*p == '/')
108             *p = '\\';
109     }
110     
111     p = pInputName;
112     /* If there is drive information, copy it over. */ 
113     if (pInputName[1] == ':') {
114         buf[0] = tolower(*p++);
115         buf[1] = *p++;
116         buf[2] = '\0';
117
118         /* If all we have is a drive letter, then we are done */
119         if (strlen(pInputName) == 2)
120             bDone = TRUE;
121     }
122     
123     q = p;
124     if (*p == '\\') {
125         p++;
126         if (*p == '\\')  /* Possible UNC name */
127         {
128             p++;
129             /* Get past the machine name.  FindFirstFile */
130             /* will not find a machine name only */
131             p = strchr(p, '\\'); 
132             if (p)
133             {
134                 p++;
135                 /* Get past the share name.  FindFirstFile */
136                 /* will not find a \\machine\share name only */
137                 p = strchr(p, '\\'); 
138                 if (p) {
139                     strncat(buf,q,p-q);
140                     q = p;
141                     p++;
142                 }
143             }
144
145             if (!p)
146                 p = q;
147         }
148     }
149
150     p = strchr(p, '\\');
151
152     while (!bDone) {
153         if (p)
154             *p = '\0';
155
156         if (strchr(q, '*') || strchr(q, '?'))
157             bFileExists = FALSE;
158
159         /* If the path exists so far, call FindFirstFile
160          * again.  However, if this portion of the path contains
161          * only '.' charaters, skip the call to FindFirstFile
162          * since it will convert '.' and '..' to actual names.
163          * Note: in the call to OnlyDots, we may have to skip
164          *       a leading slash.
165          */
166         if (bFileExists && !OnlyDots((*q == '.' ? q : q+1))) {            
167             hFind = FindFirstFile(pInputName, &wfd);
168             
169             if (hFind == INVALID_HANDLE_VALUE) {
170                 bFileExists = FALSE;
171             }
172             else {
173                 FindClose(hFind);
174
175                 if (*q == '\\')
176                     strcat(buf,"\\");
177                 strcat(buf, wfd.cFileName);
178             }
179         }
180         
181         if (!bFileExists || OnlyDots((*q == '.' ? q : q+1))) {
182             strcat(buf, q);
183         }
184         
185         if (p) {
186             q = p;
187             *p++ = '\\';
188             p = strchr(p, '\\');
189         }
190         else {
191             bDone = TRUE;
192         }
193     }
194     
195     /* First convert all slashes to / so server code handles it ok */
196     for (p = buf; *p; p++) {
197         if (*p == '\\')
198             *p = '/';
199     }
200
201     return ap_pstrdup(pPool, buf);
202 }
203
204
205 /*  Perform canonicalization with the exception that the
206  *  input case is preserved.
207  */
208 API_EXPORT(char *) ap_os_case_canonical_filename(ap_pool_t *pPool, 
209                                                  const char *szFile)
210 {
211     char *pNewStr;
212     char *s;
213     char *p; 
214     char *q;
215
216     if (szFile == NULL || strlen(szFile) == 0)
217         return ap_pstrdup(pPool, "");
218
219     pNewStr = ap_pstrdup(pPool, szFile);
220
221     /*  Change all '\' characters to '/' characters.
222      *  While doing this, remove any trailing '.'.
223      *  Also, blow away any directories with 3 or
224      *  more '.'
225      */
226     for (p = pNewStr,s = pNewStr; *s; s++,p++) {
227         if (*s == '\\' || *s == '/') {
228
229             q = p;
230             while (p > pNewStr && *(p-1) == '.')
231                 p--;
232
233             if (p == pNewStr && q-p <= 2 && *p == '.')
234                 p = q;
235             else if (p > pNewStr && p < q && *(p-1) == '/') {
236                 if (q-p > 2)
237                     p--;
238                 else
239                     p = q;
240             }
241
242             *p = '/';
243         }
244         else {
245             *p = *s;
246         }
247     }
248     *p = '\0';
249
250     /*  Blow away any final trailing '.' since on Win32
251      *  foo.bat == foo.bat. == foo.bat... etc.
252      *  Also blow away any trailing spaces since
253      *  "filename" == "filename "
254      */
255     q = p;
256     while (p > pNewStr && (*(p-1) == '.' || *(p-1) == ' '))
257         p--;
258     if ((p > pNewStr) ||
259         (p == pNewStr && q-p > 2))
260         *p = '\0';
261         
262
263     /*  One more security issue to deal with.  Win32 allows
264      *  you to create long filenames.  However, alias filenames
265      *  are always created so that the filename will
266      *  conform to 8.3 rules.  According to the Microsoft
267      *  Developer's network CD (1/98) 
268      *  "Automatically generated aliases are composed of the 
269      *   first six characters of the filename plus ~n 
270      *   (where n is a number) and the first three characters 
271      *   after the last period."
272      *  Here, we attempt to detect and decode these names.
273      */
274     p = strchr(pNewStr, '~');
275     if (p != NULL) {
276         char *pConvertedName, *pQstr, *pPstr;
277         char buf[HUGE_STRING_LEN];
278         /* We potentially have a short name.  Call 
279          * ap_os_systemcase_filename to examine the filesystem
280          * and possibly extract the long name.
281          */
282         pConvertedName = ap_os_systemcase_filename(pPool, pNewStr);
283
284         /* Since we want to preserve the incoming case as much
285          * as we can, compare for differences in the string and
286          * only substitute in the path names that changed.
287          */
288         if (stricmp(pNewStr, pConvertedName)) {
289             buf[0] = '\0';
290
291             q = pQstr = pConvertedName;
292             p = pPstr = pNewStr;
293             do {
294                 q = strchr(q,'/');
295                 p = strchr(p,'/');
296
297                 if (p != NULL) {
298                     *q = '\0';
299                     *p = '\0';
300                 }
301
302                 if (stricmp(pQstr, pPstr)) 
303                     strcat(buf, pQstr);   /* Converted name */
304                 else 
305                     strcat(buf, pPstr);   /* Original name  */
306
307
308                 if (p != NULL) {
309                     pQstr = q;
310                     pPstr = p;
311                     *q++ = '/';
312                     *p++ = '/';
313                 }
314
315             } while (p != NULL); 
316
317             pNewStr = ap_pstrdup(pPool, buf);
318         }
319     }
320
321
322     return pNewStr;
323 }
324
325 /*  Perform complete canonicalization.
326  */
327 API_EXPORT(char *) ap_os_canonical_filename(ap_pool_t *pPool, const char *szFile)
328 {
329     char *pNewName;
330     pNewName = ap_os_case_canonical_filename(pPool, szFile);
331     strlwr(pNewName);
332     return pNewName;
333 }
334
335 /*
336  * ap_os_is_filename_valid is given a filename, and returns 0 if the filename
337  * is not valid for use on this system. On Windows, this means it fails any
338  * of the tests below. Otherwise returns 1.
339  *
340  * Test for filename validity on Win32. This is of tests come in part from
341  * the MSDN article at "Technical Articles, Windows Platform, Base Services,
342  * Guidelines, Making Room for Long Filenames" although the information
343  * in MSDN about filename testing is incomplete or conflicting. There is a
344  * similar set of tests in "Technical Articles, Windows Platform, Base Services,
345  * Guidelines, Moving Unix Applications to Windows NT".
346  *
347  * The tests are:
348  *
349  * 1) total path length greater than MAX_PATH
350  *
351  * 2) anything using the octets 0-31 or characters " < > | :
352  *    (these are reserved for Windows use in filenames. In addition
353  *     each file system has its own additional characters that are
354  *     invalid. See KB article Q100108 for more details).
355  *
356  * 3) anything ending in "." (no matter how many)
357  *    (filename doc, doc. and doc... all refer to the same file)
358  *
359  * 4) any segment in which the basename (before first period) matches
360  *    one of the DOS device names
361  *    (the list comes from KB article Q100108 although some people
362  *     reports that additional names such as "COM5" are also special
363  *     devices).
364  *
365  * If the path fails ANY of these tests, the result must be to deny access.
366  */
367
368 API_EXPORT(int) ap_os_is_filename_valid(const char *file)
369 {
370     const char *segstart;
371     unsigned int seglength;
372     const char *pos;
373     static const char * const invalid_characters = "?\"<>*|:";
374     static const char * const invalid_filenames[] = { 
375         "CON", "AUX", "COM1", "COM2", "COM3", 
376         "COM4", "LPT1", "LPT2", "LPT3", "PRN", "NUL", NULL 
377     };
378
379     /* Test 1 */
380     if (strlen(file) >= MAX_PATH) {
381         /* Path too long for Windows. Note that this test is not valid
382          * if the path starts with //?/ or \\?\. */
383         return 0;
384     }
385
386     pos = file;
387
388     /* Skip any leading non-path components. This can be either a
389      * drive letter such as C:, or a UNC path such as \\SERVER\SHARE\.
390      * We continue and check the rest of the path based on the rules above.
391      * This means we could eliminate valid filenames from servers which
392      * are not running NT (such as Samba).
393      */
394
395     if (pos[0] && pos[1] == ':') {
396         /* Skip leading drive letter */
397         pos += 2;
398     }
399     else {
400         if ((pos[0] == '\\' || pos[0] == '/') &&
401             (pos[1] == '\\' || pos[1] == '/')) {
402             /* Is a UNC, so skip the server name and share name */
403             pos += 2;
404             while (*pos && *pos != '/' && *pos != '\\')
405                 pos++;
406             if (!*pos) {
407                 /* No share name */
408                 return 0;
409             }
410             pos++;      /* Move to start of share name */
411             while (*pos && *pos != '/' && *pos != '\\')
412                 pos++;
413             if (!*pos) {
414                 /* No path information */
415                 return 0;
416             }
417         }
418     }
419
420     while (*pos) {
421         unsigned int idx;
422         unsigned int baselength;
423
424         while (*pos == '/' || *pos == '\\') {
425             pos++;
426         }
427         if (*pos == '\0') {
428             break;
429         }
430         segstart = pos; /* start of segment */
431         while (*pos && *pos != '/' && *pos != '\\') {
432             pos++;
433         }
434         seglength = pos - segstart;
435         /* 
436          * Now we have a segment of the path, starting at position "segstart"
437          * and length "seglength"
438          */
439
440         /* Test 2 */
441         for (idx = 0; idx < seglength; idx++) {
442             if ((segstart[idx] > 0 && segstart[idx] < 32) ||
443                 strchr(invalid_characters, segstart[idx])) {
444                 return 0;
445             }
446         }
447
448         /* Test 3 */
449         if (segstart[seglength-1] == '.') {
450             return 0;
451         }
452
453         /* Test 4 */
454         for (baselength = 0; baselength < seglength; baselength++) {
455             if (segstart[baselength] == '.') {
456                 break;
457             }
458         }
459
460         /* baselength is the number of characters in the base path of
461          * the segment (which could be the same as the whole segment length,
462          * if it does not include any dot characters). */
463         if (baselength == 3 || baselength == 4) {
464             for (idx = 0; invalid_filenames[idx]; idx++) {
465                 if (strlen(invalid_filenames[idx]) == baselength &&
466                     !strnicmp(invalid_filenames[idx], segstart, baselength)) {
467                     return 0;
468                 }
469             }
470         }
471     }
472
473     return 1;
474 }