1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
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
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.
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.
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.
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
47 * ====================================================================
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/>.
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.
68 /* Returns TRUE if the input string is a string
69 * of one or more '.' characters.
71 static BOOL OnlyDots(char *pString)
78 for (c = pString;*c;c++)
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.
90 API_EXPORT(char *) ap_os_systemcase_filename(ap_pool_t *pPool,
93 char buf[HUGE_STRING_LEN];
97 BOOL bFileExists = TRUE;
101 if (!szFile || strlen(szFile) == 0 || strlen(szFile) >= sizeof(buf))
102 return ap_pstrdup(pPool, "");
105 pInputName = ap_pstrdup(pPool, szFile);
107 /* First convert all slashes to \ so Win32 calls work OK */
108 for (p = pInputName; *p; p++) {
114 /* If there is drive information, copy it over. */
115 if (pInputName[1] == ':') {
116 buf[0] = tolower(*p++);
120 /* If all we have is a drive letter, then we are done */
121 if (strlen(pInputName) == 2)
128 if (*p == '\\') /* Possible UNC name */
131 /* Get past the machine name. FindFirstFile */
132 /* will not find a machine name only */
137 /* Get past the share name. FindFirstFile */
138 /* will not find a \\machine\share name only */
158 if (strchr(q, '*') || strchr(q, '?'))
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
168 if (bFileExists && !OnlyDots((*q == '.' ? q : q+1))) {
169 hFind = FindFirstFile(pInputName, &wfd);
171 if (hFind == INVALID_HANDLE_VALUE) {
179 strcat(buf, wfd.cFileName);
183 if (!bFileExists || OnlyDots((*q == '.' ? q : q+1))) {
197 /* First convert all slashes to / so server code handles it ok */
198 for (p = buf; *p; p++) {
203 return ap_pstrdup(pPool, buf);
207 /* Perform canonicalization with the exception that the
208 * input case is preserved.
210 API_EXPORT(char *) ap_os_case_canonical_filename(ap_pool_t *pPool,
218 if (szFile == NULL || strlen(szFile) == 0)
219 return ap_pstrdup(pPool, "");
221 pNewStr = ap_pstrdup(pPool, szFile);
223 /* Change all '\' characters to '/' characters.
224 * While doing this, remove any trailing '.'.
225 * Also, blow away any directories with 3 or
228 for (p = pNewStr,s = pNewStr; *s; s++,p++) {
229 if (*s == '\\' || *s == '/') {
232 while (p > pNewStr && *(p-1) == '.')
235 if (p == pNewStr && q-p <= 2 && *p == '.')
237 else if (p > pNewStr && p < q && *(p-1) == '/') {
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 "
258 while (p > pNewStr && (*(p-1) == '.' || *(p-1) == ' '))
261 (p == pNewStr && q-p > 2))
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.
276 p = strchr(pNewStr, '~');
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.
284 pConvertedName = ap_os_systemcase_filename(pPool, pNewStr);
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.
290 if (stricmp(pNewStr, pConvertedName)) {
293 q = pQstr = pConvertedName;
304 if (stricmp(pQstr, pPstr))
305 strcat(buf, pQstr); /* Converted name */
307 strcat(buf, pPstr); /* Original name */
319 pNewStr = ap_pstrdup(pPool, buf);
327 /* Perform complete canonicalization.
329 API_EXPORT(char *) ap_os_canonical_filename(ap_pool_t *pPool, const char *szFile)
332 pNewName = ap_os_case_canonical_filename(pPool, szFile);
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)
346 API_EXPORT(int) os_stat(const char *szPath, struct stat *pStat)
350 if (strlen(szPath) == 0) {
354 if (szPath[0] == '/' && szPath[1] == '/') {
359 ap_assert(strlen(szPath) < _MAX_PATH);
361 for (s = buf; *s; ++s) {
367 /* then we need to add one more to get \\machine\share\ */
372 return stat(buf, pStat);
376 * Below removes the trailing /, however, do not remove
377 * it in the case of 'x:/' or stat will fail
380 if ((szPath[n - 1] == '\\' || szPath[n - 1] == '/') &&
381 !(n == 3 && szPath[1] == ':')) {
384 ap_assert(n < _MAX_PATH);
388 return stat(buf, pStat);
390 return stat(szPath, pStat);
396 /* Partial replacement for strftime. This adds certain expandos to the
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
406 char *new_format = (char *) _alloca(max + 11);
407 size_t i, j, format_length = strlen(format);
411 for (i = 0, j = 0; (i < format_length && j < max);) {
412 if (format[i] != '%') {
413 new_format[j++] = format[i++];
416 switch (format[i+1]) {
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);
425 memcpy(new_format + j, "%I:%M:%S %p", 11);
430 memcpy(new_format + j, "%H:%M:%S", 8);
435 length_written = ap_snprintf(new_format + j, max - j, "%2d",
437 j = (length_written == -1) ? max : (j + length_written);
441 /* We know we can advance two characters forward here. */
442 new_format[j++] = format[i++];
443 new_format[j++] = format[i++];
447 *s = '\0'; /* Defensive programming, okay since output is undefined */
450 new_format[j] = '\0';
451 return_value = strftime(s, max, new_format, tm);
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.
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".
470 * 1) total path length greater than MAX_PATH
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).
477 * 3) anything ending in "." (no matter how many)
478 * (filename doc, doc. and doc... all refer to the same file)
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
486 * If the path fails ANY of these tests, the result must be to deny access.
489 API_EXPORT(int) ap_os_is_filename_valid(const char *file)
491 const char *segstart;
492 unsigned int seglength;
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
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 \\?\. */
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).
516 if (pos[0] && pos[1] == ':') {
517 /* Skip leading drive letter */
521 if ((pos[0] == '\\' || pos[0] == '/') &&
522 (pos[1] == '\\' || pos[1] == '/')) {
523 /* Is a UNC, so skip the server name and share name */
525 while (*pos && *pos != '/' && *pos != '\\')
531 pos++; /* Move to start of share name */
532 while (*pos && *pos != '/' && *pos != '\\')
535 /* No path information */
543 unsigned int baselength;
545 while (*pos == '/' || *pos == '\\') {
551 segstart = pos; /* start of segment */
552 while (*pos && *pos != '/' && *pos != '\\') {
555 seglength = pos - segstart;
557 * Now we have a segment of the path, starting at position "segstart"
558 * and length "seglength"
562 for (idx = 0; idx < seglength; idx++) {
563 if ((segstart[idx] > 0 && segstart[idx] < 32) ||
564 strchr(invalid_characters, segstart[idx])) {
570 if (segstart[seglength-1] == '.') {
575 for (baselength = 0; baselength < seglength; baselength++) {
576 if (segstart[baselength] == '.') {
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)) {