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;