diff options
Diffstat (limited to 'APACHE_1_3_42/src/os/win32/util_win32.c')
-rw-r--r-- | APACHE_1_3_42/src/os/win32/util_win32.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/APACHE_1_3_42/src/os/win32/util_win32.c b/APACHE_1_3_42/src/os/win32/util_win32.c new file mode 100644 index 0000000000..104e9dc2bc --- /dev/null +++ b/APACHE_1_3_42/src/os/win32/util_win32.c @@ -0,0 +1,530 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 + +#include <sys/stat.h> +#include <stdarg.h> +#include <time.h> +#include <stdlib.h> + +#include "httpd.h" +#include "http_log.h" + +/* 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. + * + * WARNING: Folding to systemcase fails when /path/to/foo/../bar + * is given and foo does not exist, is not a directory. + */ +API_EXPORT(char *) ap_os_systemcase_filename(pool *pPool, + const char *szFile) +{ + char *buf, *t, *r; + const char *q, *p; + BOOL bDone = FALSE; + BOOL bFileExists = TRUE; + HANDLE hFind; + WIN32_FIND_DATA wfd; + size_t buflen; + int slack = 0; + + if (!szFile || strlen(szFile) == 0) + return ap_pstrdup(pPool, ""); + + buflen = strlen(szFile); + t = buf = ap_palloc(pPool, buflen + 1); + q = szFile; + + /* If there is drive information, copy it over. */ + if (szFile[1] == ':') { + /* Lowercase, so that when systemcase is used for + * comparison, d: designations will match + */ + *(t++) = tolower(*(q++)); + *(t++) = *(q++); + } + else if ((*q == '/') || (*q == '\\')) { + /* Get past the root path (/ or //foo/bar/) so we can go + * on to normalize individual path elements. + */ + *(t++) = '\\', ++q; + if ((*q == '/') || (*q == '\\')) /* UNC name */ + { + /* Lower-case the machine name, so compares match. + * FindFirstFile won't parse \\machine alone + */ + *(t++) = '\\', ++q; + for (p = q; *p && (*p != '/') && (*p != '\\'); ++p) + /* continue */ ; + if (*p || p > q) + { + /* Lower-case the machine name, so compares match. + * FindFirstFile won't parse \\machine\share alone + */ + memcpy(t, q, p - q); + t[p - q] = '\0'; + strlwr(t); + t += p - q; + q = p; + if (*p) { + *(t++) = '\\', ++q; + for (p = q; *p && (*p != '/') && (*p != '\\'); ++p) + /* continue */ ; + if (*p || p > q) + { + /* Copy the lower-cased share name. FindFirstFile + * cannot not find a \\machine\share name only + */ + memcpy(t, q, p - q); + t[p - q] = '\0'; + strlwr(t); + t += p - q; + q = p; + if (*p) + *(t++) = '\\', ++q; + else + bFileExists = FALSE; + } + else + bFileExists = FALSE; + } + else + bFileExists = FALSE; + } + else + bFileExists = FALSE; + } + } + + while (bFileExists) { + + /* parse past any leading slashes */ + for (; (*q == '/') || (*q == '\\'); ++q) + *(t++) = '\\'; + + /* break on end of string */ + if (!*q) + break; + + /* get to the end of this path segment */ + for (p = q; *p && (*p != '/') && (*p != '\\'); ++p) + /* continue */ ; + + /* copy the segment */ + memcpy(t, q, p - q); + t[p - q] = '\0'; + + /* Test for nasties that can exhibit undesired effects */ + if (strpbrk(t, "?\"<>*|:")) { + t += p - q; + q = p; + break; + } + + /* 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. + * On win32, '...' is an alias for '..', so we gain + * a bit of slack. + */ + if (*t == '.' && OnlyDots(t)) { + if (p - q == 3) { + t += 2; + q = p; + ++slack; + } + else { + t += p - q; + q = p; + } + /* Paths of 4 dots or more are invalid */ + if (p - q > 3) + break; + } + else { + if ((hFind = FindFirstFile(buf, &wfd)) == INVALID_HANDLE_VALUE) { + t += p - q; + q = p; + break; + } + else { + size_t fnlen = strlen(wfd.cFileName); + FindClose(hFind); + /* the string length just changed, could have shrunk + * (trailing spaces or dots) or could have grown + * (longer filename aliases). Realloc as necessary + */ + slack -= fnlen - (p - q); + if (slack < 0) { + char *n; + slack += buflen + fnlen - (p - q); + buflen += buflen + fnlen - (p - q); + n = ap_palloc(pPool, buflen + 1); + memcpy (n, buf, t - buf); + t = n + (t - buf); + buf = n; + } + memcpy(t, wfd.cFileName, fnlen); + t += fnlen; + q = p; + if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + break; + } + } + } + + /* Convert all parsed '\'s to '/' for canonical form (doesn't touch + * the non-existant portion of the path whatsoever.) + */ + for (r = buf; r < t; ++r) { + if (*r == '\\') + *r = '/'; + } + + /* Copy the non-existant portion (minimally nul-terminates the string) */ + strcpy(t, q); + + return buf; +} + + +/* Perform canonicalization with the exception that the + * input case is preserved. + */ +API_EXPORT(char *) ap_os_case_canonical_filename(pool *pPool, + const char *szFile) +{ + 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 '.' + */ + for (p = pNewStr,s = pNewStr; *s; s++,p++) { + if (*s == '\\' || *s == '/') { + + 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; + } + } + *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. + * + * XXX: Netware network clients may have alternate short names, + * simply truncated, with no embedded '~'. Further, this behavior + * can be modified on WinNT volumes. This was not a safe test, + * therefore exclude the '~' pretest. + */ +#ifdef WIN32_SHORT_FILENAME_INSECURE_BEHAVIOR + p = strchr(pNewStr, '~'); + if (p != NULL) +#endif + /* ap_os_systemcase_filename now changes the case of only + * the pathname elements that are found. + */ + pNewStr = ap_os_systemcase_filename(pPool, pNewStr); + + 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; +} + + +/* + * ap_os_is_filename_valid is given a filename, and returns 0 if the filename + * is not valid for use on this system. On Windows, this means it fails any + * of the tests below. Otherwise returns 1. + * + * Test for filename validity on Win32. This is of tests come in part from + * the MSDN article at "Technical Articles, Windows Platform, Base Services, + * Guidelines, Making Room for Long Filenames" although the information + * in MSDN about filename testing is incomplete or conflicting. There is a + * similar set of tests in "Technical Articles, Windows Platform, Base Services, + * Guidelines, Moving Unix Applications to Windows NT". + * + * The tests are: + * + * 1) total path length greater than MAX_PATH + * + * 2) anything using the octets 0-31 or characters " < > | : + * (these are reserved for Windows use in filenames. In addition + * each file system has its own additional characters that are + * invalid. See KB article Q100108 for more details). + * + * 3) anything ending in "." (no matter how many) + * (filename doc, doc. and doc... all refer to the same file) + * + * 4) any segment in which the basename (before first period) matches + * one of the DOS device names + * (the list comes from KB article Q100108 although some people + * reports that additional names such as "COM5" are also special + * devices). + * + * If the path fails ANY of these tests, the result must be to deny access. + */ + +API_EXPORT(int) ap_os_is_filename_valid(const char *file) +{ + const char *segstart; + unsigned int seglength; + const char *pos; + static const char * const invalid_characters = "?\"<>*|:"; + static const char * const invalid_filenames[] = { + "CON", "AUX", "COM1", "COM2", "COM3", + "COM4", "LPT1", "LPT2", "LPT3", "PRN", "NUL", NULL + }; + + /* Test 1 */ + if (strlen(file) >= MAX_PATH) { + /* Path too long for Windows. Note that this test is not valid + * if the path starts with //?/ or \\?\. */ + return 0; + } + + pos = file; + + /* Skip any leading non-path components. This can be either a + * drive letter such as C:, or a UNC path such as \\SERVER\SHARE\. + * We continue and check the rest of the path based on the rules above. + * This means we could eliminate valid filenames from servers which + * are not running NT (such as Samba). + */ + + if (pos[0] && pos[1] == ':') { + /* Skip leading drive letter */ + pos += 2; + } + else { + if ((pos[0] == '\\' || pos[0] == '/') && + (pos[1] == '\\' || pos[1] == '/')) { + /* Is a UNC, so skip the server name and share name */ + pos += 2; + while (*pos && *pos != '/' && *pos != '\\') + pos++; + if (!*pos) { + /* No share name */ + return 0; + } + pos++; /* Move to start of share name */ + while (*pos && *pos != '/' && *pos != '\\') + pos++; + if (!*pos) { + /* No path information */ + return 0; + } + } + } + + while (*pos) { + unsigned int idx; + unsigned int baselength; + + while (*pos == '/' || *pos == '\\') { + pos++; + } + if (*pos == '\0') { + break; + } + segstart = pos; /* start of segment */ + while (*pos && *pos != '/' && *pos != '\\') { + pos++; + } + seglength = pos - segstart; + /* + * Now we have a segment of the path, starting at position "segstart" + * and length "seglength" + */ + + /* Test 2 */ + for (idx = 0; idx < seglength; idx++) { + if ((segstart[idx] > 0 && segstart[idx] < 32) || + strchr(invalid_characters, segstart[idx])) { + return 0; + } + } + + /* Test 3 */ + if (segstart[seglength-1] == '.') { + return 0; + } + + /* Test 4 */ + for (baselength = 0; baselength < seglength; baselength++) { + if (segstart[baselength] == '.') { + break; + } + } + + /* baselength is the number of characters in the base path of + * the segment (which could be the same as the whole segment length, + * if it does not include any dot characters). */ + if (baselength == 3 || baselength == 4) { + for (idx = 0; invalid_filenames[idx]; idx++) { + if (strlen(invalid_filenames[idx]) == baselength && + !strnicmp(invalid_filenames[idx], segstart, baselength)) { + return 0; + } + } + } + } + + return 1; +} + + +API_EXPORT(ap_os_dso_handle_t) ap_os_dso_load(const char *module_name) +{ + UINT em; + ap_os_dso_handle_t dsoh; + char path[MAX_PATH], *p; + /* Load the module... + * per PR2555, the LoadLibraryEx function is very picky about slashes. + * Debugging on NT 4 SP 6a reveals First Chance Exception within NTDLL. + * LoadLibrary in the MS PSDK also reveals that it -explicitly- states + * that backslashes must be used. + * + * Transpose '\' for '/' in the filename. + */ + ap_cpystrn(path, module_name, MAX_PATH); + p = path; + while (p = strchr(p, '/')) + *p = '\\'; + + /* First assume the dso/dll's required by -this- dso are sitting in the + * same path or can be found in the usual places. Failing that, let's + * let that dso look in the apache root. + */ + em = SetErrorMode(SEM_FAILCRITICALERRORS); + dsoh = LoadLibraryEx(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!dsoh) { + dsoh = LoadLibraryEx(path, NULL, 0); + } + SetErrorMode(em); + return dsoh; +} + +API_EXPORT(const char *) ap_os_dso_error(void) +{ + int len, nErrorCode; + static char errstr[120]; + /* This is -not- threadsafe code, but it's about the best we can do. + * mostly a potential problem for isapi modules, since LoadModule + * errors are handled within a single config thread. + */ + + nErrorCode = GetLastError(); + len = ap_snprintf(errstr, sizeof(errstr), "(%d) ", nErrorCode); + + len += FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + nErrorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ + (LPTSTR) errstr + len, + sizeof(errstr) - len, + NULL + ); + /* FormatMessage may have appended a newline (\r\n). So remove it + * and use ": " instead like the Unix errors. The error may also + * end with a . before the return - if so, trash it. + */ + if (len > 1 && errstr[len-2] == '\r' && errstr[len-1] == '\n') { + if (len > 2 && errstr[len-3] == '.') + len--; + errstr[len-2] = ':'; + errstr[len-1] = ' '; + } + return errstr; +} + +#endif /* WIN32 */ |