coar 98/11/05 12:07:53
Modified: src CHANGES src/include httpd.h src/main http_request.c src/os/win32 util_win32.c Log: Simplify the Win32 os_demoniacal_fil.. er, os_canonical_filename() routine so that it's a bit more deterministic. PR: 2555, 2915, 3064, 3232 Submitted by: Ken Parzygnat <[EMAIL PROTECTED]> Reviewed by: Ben Laurie, Marc Slemko Revision Changes Path 1.1135 +4 -0 apache-1.3/src/CHANGES Index: CHANGES =================================================================== RCS file: /export/home/cvs/apache-1.3/src/CHANGES,v retrieving revision 1.1134 retrieving revision 1.1135 diff -u -r1.1134 -r1.1135 --- CHANGES 1998/11/05 19:20:14 1.1134 +++ CHANGES 1998/11/05 20:07:48 1.1135 @@ -1,5 +1,9 @@ Changes with Apache 1.3.4 + *) Rework os_canonical_*() on Win32 so it's simpler, more + robust, and works. [Ken Parzygnat <[EMAIL PROTECTED]>] + PR#2555, 2915, 3064, 3232 + *) Work around incomplete implementation of strftime on Win32. [Manoj Kasichainula, Ken Parzygnat <[EMAIL PROTECTED]>] 1.250 +9 -0 apache-1.3/src/include/httpd.h Index: httpd.h =================================================================== RCS file: /export/home/cvs/apache-1.3/src/include/httpd.h,v retrieving revision 1.249 retrieving revision 1.250 diff -u -r1.249 -r1.250 --- httpd.h 1998/10/28 19:26:28 1.249 +++ httpd.h 1998/11/05 20:07:51 1.250 @@ -998,8 +998,17 @@ #ifndef HAVE_CANONICAL_FILENAME #define ap_os_canonical_filename(p,f) (f) +#define ap_os_case_canonical_filename(p,f) (f) +#define ap_os_systemcase_filename(p,f) (f) #else API_EXPORT(char *) ap_os_canonical_filename(pool *p, const char *file); +#ifdef WIN32 +API_EXPORT(char *) ap_os_case_canonical_filename(pool *pPool, const char *szFile); +API_EXPORT(char *) ap_os_systemcase_filename(pool *pPool, const char *szFile); +#else +#define ap_os_case_canonical_filename(p,f) ap_os_canonical_filename(p,f) +#define ap_os_systemcase_filename(p,f) ap_os_canonical_filename(p,f) +#endif #endif #ifdef _OSD_POSIX 1.136 +8 -5 apache-1.3/src/main/http_request.c Index: http_request.c =================================================================== RCS file: /export/home/cvs/apache-1.3/src/main/http_request.c,v retrieving revision 1.135 retrieving revision 1.136 diff -u -r1.135 -r1.136 --- http_request.c 1998/10/20 17:42:43 1.135 +++ http_request.c 1998/11/05 20:07:52 1.136 @@ -359,16 +359,19 @@ return OK; } - r->filename = ap_os_canonical_filename(r->pool, r->filename); - test_filename = ap_pstrdup(r->pool, r->filename); - - ap_no2slash(test_filename); - num_dirs = ap_count_dirs(test_filename); + r->filename = ap_os_case_canonical_filename(r->pool, r->filename); res = get_path_info(r); if (res != OK) { return res; } + + r->filename = ap_os_canonical_filename(r->pool, r->filename); + + test_filename = ap_pstrdup(r->pool, r->filename); + + ap_no2slash(test_filename); + num_dirs = ap_count_dirs(test_filename); if ((res = check_safe_file(r))) { return res; 1.28 +254 -200 apache-1.3/src/os/win32/util_win32.c Index: util_win32.c =================================================================== RCS file: /export/home/cvs/apache-1.3/src/os/win32/util_win32.c,v retrieving revision 1.27 retrieving revision 1.28 diff -u -r1.27 -r1.28 --- util_win32.c 1998/11/05 19:20:17 1.27 +++ util_win32.c 1998/11/05 20:07:53 1.28 @@ -7,214 +7,275 @@ #include "httpd.h" #include "http_log.h" -/* Returns TRUE if the path is real, FALSE if it is PATH_INFO */ -static BOOL sub_canonical_filename(char *szCanon, unsigned nCanon, - const char *szInFile) +/* Returns TRUE if the input string is a string + * of one or more '.' characters. + */ +static BOOL OnlyDots(char *pString) { + char *c; + + if (*pString == '\0') + return FALSE; + + for (c = pString;*c;c++) + if (*c != '.') + return FALSE; + + return TRUE; +} + +/* Accepts as input a pathname, and tries to match it to an + * existing path and return the pathname in the case that + * is present on the existing path. This routine also + * converts alias names to long names. + */ +API_EXPORT(char *) ap_os_systemcase_filename(pool *pPool, + const char *szFile) +{ char buf[HUGE_STRING_LEN]; - int n; - char *szFilePart; - char *s; - int nSlashes; - WIN32_FIND_DATA d; - HANDLE h; - const char *szFile; - - szFile = szInFile; - s = strrchr(szFile, '\\'); - for (nSlashes = 0; s > szFile && s[-1] == '\\'; ++nSlashes, --s) - ; - - if (strlen(szFile)==2 && szFile[1]==':') { - /* - * If the file name is x:, do not call GetFullPathName - * because it will use the current path of the executable + char *pInputName; + char *p, *q; + BOOL bDone = FALSE; + BOOL bFileExists = TRUE; + HANDLE hFind; + WIN32_FIND_DATA wfd; + + if (!szFile || strlen(szFile) == 0 || strlen(szFile) >= sizeof(buf)) + return ap_pstrdup(pPool, ""); + + buf[0] = '\0'; + pInputName = ap_pstrdup(pPool, szFile); + + /* First convert all slashes to \ so Win32 calls work OK */ + for (p = pInputName; *p; p++) { + if (*p == '/') + *p = '\\'; + } + + p = pInputName; + /* If there is drive information, copy it over. */ + if (pInputName[1] == ':') { + buf[0] = tolower(*p++); + buf[1] = *p++; + buf[2] = '\0'; + + /* If all we have is a drive letter, then we are done */ + if (strlen(pInputName) == 2) + bDone = TRUE; + } + + q = p; + if (*p == '\\') { + p++; + if (*p == '\\') /* Possible UNC name */ + { + p++; + /* Get past the machine name. FindFirstFile */ + /* will not find a machine name only */ + p = strchr(p, '\\'); + if (p) + { + p++; + /* Get past the share name. FindFirstFile */ + /* will not find a \\machine\share name only */ + p = strchr(p, '\\'); + if (p) { + strncat(buf,q,p-q); + q = p; + p++; + } + } + + if (!p) + p = q; + } + } + + p = strchr(p, '\\'); + + while (!bDone) { + if (p) + *p = '\0'; + + if (strchr(q, '*') || strchr(q, '?')) + bFileExists = FALSE; + + /* If the path exists so far, call FindFirstFile + * again. However, if this portion of the path contains + * only '.' charaters, skip the call to FindFirstFile + * since it will convert '.' and '..' to actual names. + * Note: in the call to OnlyDots, we may have to skip + * a leading slash. */ - strcpy(buf,szFile); - n = strlen(buf); - szFilePart = buf + n; - } - else { - n = GetFullPathName(szFile, sizeof buf, buf, &szFilePart); - } - ap_assert(n); - ap_assert(n < sizeof buf); - - /* - * There is an implicit assumption that szInFile will contain a '\'. - * If this is not true (as in the case of <Directory *> or - * <File .htaccess>) we would assert in some of the code below. Therefore, - * if we don't get any '\' in the file name, then use the file name we get - * from GetFullPathName, because it will have at least one '\'. If there - * is no '\' in szInFile, it must just be a file name, so it should be - * valid to use the name from GetFullPathName. Be sure to adjust the - * 's' variable so the rest of the code functions normally. - * Note it is possible to get here when szFile == 'x:', but that is OK - * because we will bail out of this routine early. - */ - if (!s) { - szFile = buf; - s = strrchr(szFile, '\\'); - } - - /* If we have \\machine\share, convert to \\machine\share\ */ - if (buf[0] == '\\' && buf[1] == '\\') { - char *s = strchr(buf + 2, '\\'); - if (s && !strchr(s + 1, '\\')) { - strcat(s + 1, "\\"); - } - } - - if (!strchr(buf, '*') && !strchr(buf, '?')) { - h = FindFirstFile(buf, &d); - if (h != INVALID_HANDLE_VALUE) { - FindClose(h); - } - } - else { - h = INVALID_HANDLE_VALUE; - } - - if (szFilePart < buf + 3) { - ap_assert(strlen(buf) < nCanon); - strcpy(szCanon, buf); - /* a \ at the start means it is UNC, otherwise it is x: */ - if (szCanon[0] != '\\') { - ap_assert(ap_isalpha(szCanon[0])); - ap_assert(szCanon[1] == ':'); - szCanon[2] = '/'; - } - else { - char *s; - - ap_assert(szCanon[1] == '\\'); - for (s = szCanon; *s; ++s) { - if (*s == '\\') { - *s = '/'; - } - } - } - return TRUE; - } - if (szFilePart != buf + 3) { - char b2[_MAX_PATH]; - char b3[_MAX_PATH]; - ap_assert(szFilePart > buf + 3); - /* avoid SEGVs on things like "Directory *" */ - ap_assert(s >= szFile && "this is a known bug"); - - memcpy(b3, szFile, s - szFile); - b3[s - szFile] = '\0'; - -/* szFilePart[-1] = '\0'; */ - sub_canonical_filename(b2, sizeof b2, b3); - - ap_assert(strlen(b2)+1 < nCanon); - strcpy(szCanon, b2); - strcat(szCanon, "/"); - } - else { - ap_assert(strlen(buf) < nCanon); - strcpy(szCanon, buf); - szCanon[2] = '/'; - szCanon[3] = '\0'; - } - if (h == INVALID_HANDLE_VALUE) { - ap_assert(strlen(szCanon) + strlen(szFilePart) + nSlashes < nCanon); - for (n = 0; n < nSlashes; ++n) { - strcat(szCanon, "/"); - } - strcat(szCanon, szFilePart); - return FALSE; - } - else { - ap_assert(strlen(szCanon)+strlen(d.cFileName) < nCanon); - strlwr(d.cFileName); - strcat(szCanon, d.cFileName); - return TRUE; + if (bFileExists && !OnlyDots((*q == '.' ? q : q+1))) { + hFind = FindFirstFile(pInputName, &wfd); + + if (hFind == INVALID_HANDLE_VALUE) { + bFileExists = FALSE; + } + else { + FindClose(hFind); + + if (*q == '\\') + strcat(buf,"\\"); + strcat(buf, wfd.cFileName); + } + } + + if (!bFileExists || OnlyDots((*q == '.' ? q : q+1))) { + strcat(buf, q); + } + + if (p) { + q = p; + *p++ = '\\'; + p = strchr(p, '\\'); + } + else { + bDone = TRUE; + } + } + + /* First convert all slashes to / so server code handles it ok */ + for (p = buf; *p; p++) { + if (*p == '\\') + *p = '/'; } + + return ap_pstrdup(pPool, buf); } + -/* UNC requires backslashes, hence the conversion before canonicalisation. - * Not sure how * many backslashes (could be that - * \\machine\share\some/path/is/ok for example). For now, do them all. +/* Perform canonicalization with the exception that the + * input case is preserved. */ -API_EXPORT(char *) ap_os_canonical_filename(pool *pPool, const char *szFile) +API_EXPORT(char *) ap_os_case_canonical_filename(pool *pPool, + const char *szFile) { - char buf[HUGE_STRING_LEN]; - char b2[HUGE_STRING_LEN]; - const char *s; - char *d; - int nSlashes = 0; - - ap_assert(strlen(szFile) < sizeof b2); - - /* Eliminate directories consisting of three or more dots. - * These act like ".." but are not detected by other machinery. - * Also get rid of trailing .s on any path component, which are ignored - * by the filesystem. Simultaneously, rewrite / to \. - * This is a bit of a kludge - Ben. + char *pNewStr; + char *s; + char *p; + char *q; + + if (szFile == NULL || strlen(szFile) == 0) + return ap_pstrdup(pPool, ""); + + pNewStr = ap_pstrdup(pPool, szFile); + + /* Change all '\' characters to '/' characters. + * While doing this, remove any trailing '.'. + * Also, blow away any directories with 3 or + * more '.' */ - if (strlen(szFile) == 1) { - /* - * If the file is only one char (like in the case of / or .) then - * just pass that through to sub_canonical_filename. Convert a - * '/' to '\\' if necessary. - */ - if (szFile[0] == '/') { - b2[0] = '\\'; - } - else { - b2[0] = szFile[0]; - } + for (p = pNewStr,s = pNewStr; *s; s++,p++) { + if (*s == '\\' || *s == '/') { - b2[1] = '\0'; + q = p; + while (p > pNewStr && *(p-1) == '.') + p--; + + if (p == pNewStr && q-p <= 2 && *p == '.') + p = q; + else if (p > pNewStr && p < q && *(p-1) == '/') { + if (q-p > 2) + p--; + else + p = q; + } + + *p = '/'; + } + else { + *p = *s; + } } - else { - for (s = szFile, d = b2; (*d = *s); ++d, ++s) { - if (*s == '/') { - *d = '\\'; - } - if (*s == '.' && (s[1] == '/' || s[1] == '\\' || !s[1])) { - while (*d == '.') { - --d; - } - if (*d == '\\') { - --d; - } - } - } - - /* Finally, a trailing slash(es) screws thing, so blow them away */ - for (nSlashes = 0; d > b2 && d[-1] == '\\'; --d, ++nSlashes) - ; - /* XXXX this breaks '/' and 'c:/' cases */ - *d = '\0'; - } - sub_canonical_filename(buf, sizeof buf, b2); - - buf[0] = ap_tolower(buf[0]); - - if (nSlashes) { - /* - * If there were additional trailing slashes, add them back on. - * Be sure not to add more than were originally there though, - * by checking to see if sub_canonical_filename added one; - * this could happen in cases where the file name is 'd:/' + *p = '\0'; + + /* Blow away any final trailing '.' since on Win32 + * foo.bat == foo.bat. == foo.bat... etc. + * Also blow away any trailing spaces since + * "filename" == "filename " + */ + q = p; + while (p > pNewStr && (*(p-1) == '.' || *(p-1) == ' ')) + p--; + if ((p > pNewStr) || + (p == pNewStr && q-p > 2)) + *p = '\0'; + + + /* One more security issue to deal with. Win32 allows + * you to create long filenames. However, alias filenames + * are always created so that the filename will + * conform to 8.3 rules. According to the Microsoft + * Developer's network CD (1/98) + * "Automatically generated aliases are composed of the + * first six characters of the filename plus ~n + * (where n is a number) and the first three characters + * after the last period." + * Here, we attempt to detect and decode these names. + */ + p = strchr(pNewStr, '~'); + if (p != NULL) { + char *pConvertedName, *pQstr, *pPstr; + char buf[HUGE_STRING_LEN]; + /* We potentially have a short name. Call + * ap_os_systemcase_filename to examine the filesystem + * and possibly extract the long name. + */ + pConvertedName = ap_os_systemcase_filename(pPool, pNewStr); + + /* Since we want to preserve the incoming case as much + * as we can, compare for differences in the string and + * only substitute in the path names that changed. */ - ap_assert(strlen(buf)+nSlashes < sizeof buf); + if (stricmp(pNewStr, pConvertedName)) { + buf[0] = '\0'; + + q = pQstr = pConvertedName; + p = pPstr = pNewStr; + do { + q = strchr(q,'/'); + p = strchr(p,'/'); + + if (p != NULL) { + *q = '\0'; + *p = '\0'; + } + + if (stricmp(pQstr, pPstr)) + strcat(buf, pQstr); /* Converted name */ + else + strcat(buf, pPstr); /* Original name */ + + + if (p != NULL) { + pQstr = q; + pPstr = p; + *q++ = '/'; + *p++ = '/'; + } - if (nSlashes && buf[strlen(buf)-1] == '/') - nSlashes--; + } while (p != NULL); - while (nSlashes--) { - strcat(buf, "/"); + pNewStr = ap_pstrdup(pPool, buf); } } - return ap_pstrdup(pPool, buf); + + return pNewStr; } +/* Perform complete canonicalization. + */ +API_EXPORT(char *) ap_os_canonical_filename(pool *pPool, const char *szFile) +{ + char *pNewName; + pNewName = ap_os_case_canonical_filename(pPool, szFile); + strlwr(pNewName); + return pNewName; +} + /* Win95 doesn't like trailing /s. NT and Unix don't mind. This works * around the problem. * Errr... except if it is UNC and we are referring to the root of @@ -227,19 +288,12 @@ API_EXPORT(int) os_stat(const char *szPath, struct stat *pStat) { int n; - - /* be sure it is has a drive letter or is a UNC path; everything - * _must_ be canonicalized before getting to this point. - */ - if (szPath[1] != ':' && szPath[1] != '/') { - ap_log_error(APLOG_MARK, APLOG_ERR, NULL, - "Invalid path in os_stat: \"%s\", " - "should have a drive letter or be a UNC path", - szPath); - return (-1); + + if (strlen(szPath) == 0) { + return -1; } - if (szPath[0] == '/') { + if (szPath[0] == '/' && szPath[1] == '/') { char buf[_MAX_PATH]; char *s; int nSlashes = 0;