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