util_win32.c revision b4c8a80f7dbfc9b56dbe03bdc28f0b5eb5f23697
#include <windows.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.
*/
{
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.
*/
const char *szFile)
{
char buf[HUGE_STRING_LEN];
char *pInputName;
char *p, *q;
buf[0] = '\0';
/* 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. */
buf[1] = *p++;
/* If all we have is a drive letter, then we are done */
}
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) {
q = p;
p++;
}
}
if (!p)
p = q;
}
}
p = strchr(p, '\\');
while (!bDone) {
if (p)
*p = '\0';
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.
*/
if (hFind == INVALID_HANDLE_VALUE) {
bFileExists = FALSE;
}
else {
if (*q == '\\')
}
}
}
if (p) {
q = p;
*p++ = '\\';
p = strchr(p, '\\');
}
else {
}
}
/* First convert all slashes to / so server code handles it ok */
for (p = buf; *p; p++) {
if (*p == '\\')
*p = '/';
}
}
/* Perform canonicalization with the exception that the
* input case is preserved.
*/
const char *szFile)
{
char *pNewStr;
char *s;
char *p;
char *q;
/* Change all '\' characters to '/' characters.
* While doing this, remove any trailing '.'.
* Also, blow away any directories with 3 or
* more '.'
*/
if (*s == '\\' || *s == '/') {
q = p;
p--;
p = q;
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;
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.
*/
if (p != NULL) {
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.
*/
/* 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.
*/
buf[0] = '\0';
q = pQstr = pConvertedName;
do {
q = strchr(q,'/');
p = strchr(p,'/');
if (p != NULL) {
*q = '\0';
*p = '\0';
}
else
if (p != NULL) {
pQstr = q;
pPstr = p;
*q++ = '/';
*p++ = '/';
}
} while (p != NULL);
}
}
return pNewStr;
}
/* Perform complete canonicalization.
*/
{
char *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
* the UNC, we MUST have a trailing \ and we can't use /s. Jeez.
* Not sure if this refers to all UNCs or just roots,
* but I'm going to fix it for all cases for now. (Ben)
*/
{
int n;
return -1;
}
char *s;
int nSlashes = 0;
for (s = buf; *s; ++s) {
if (*s == '/') {
*s = '\\';
++nSlashes;
}
}
/* then we need to add one more to get \\machine\share\ */
if (nSlashes == 3) {
*s++ = '\\';
}
*s = '\0';
}
/*
* Below removes the trailing /, however, do not remove
* it in the case of 'x:/' or stat will fail
*/
}
}
/* Fix two really crap problems with Win32 spawn[lv]e*:
*
* 1. Win32 doesn't deal with spaces in argv.
* 2. Win95 doesn't like / in cmdname.
*/
const char *const *argv)
{
int n;
char **aszArgs;
const char *szArg;
char *szCmd;
char *s;
for (s = szCmd; *s; ++s) {
if (*s == '/') {
*s = '\\';
}
}
for (n = 0; argv[n]; ++n)
;
aszArgs[n][0] = '"';
}
else {
}
}
}
{
int n;
char **aszArgs;
const char *szArg;
char *szCmd;
char *s;
for (s = szCmd; *s; ++s) {
if (*s == '/') {
*s = '\\';
}
}
for (n = 0; argv[n]; ++n)
;
aszArgs[n][0] = '"';
}
else {
}
}
}
{
int n;
char **aszArgs;
const char *szArg;
const char *const *aszEnv;
char *szCmd;
char *s;
for (s = szCmd; *s; ++s) {
if (*s == '/') {
*s = '\\';
}
}
;
aszArgs[n][0] = '"';
}
else {
}
}
}
/* Partial replacement for strftime. This adds certain expandos to the
* Windows version
*/
/* If the new format string is bigger than max, the result string probably
* won't fit anyway. When %-expandos are added, made sure the padding below
* is enough.
*/
int return_value;
int length_written;
for (i = 0, j = 0; (i < format_length && j < max);) {
if (format[i] != '%') {
new_format[j++] = format[i++];
continue;
}
switch (format[i+1]) {
case 'D':
/* Is this locale dependent? Shouldn't be...
Also note the year 2000 exposure here */
i += 2;
j += 8;
break;
case 'r':
i += 2;
j += 11;
break;
case 'T':
i += 2;
j += 8;
break;
case 'e':
i += 2;
break;
default:
/* We know we can advance two characters forward here. */
new_format[j++] = format[i++];
new_format[j++] = format[i++];
}
}
if (j >= max) {
*s = '\0'; /* Defensive programming, okay since output is undefined */
return_value = 0;
} else {
new_format[j] = '\0';
}
return return_value;
}
/*
* 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.
*/
{
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",
};
/* Test 1 */
/* Path too long for Windows. Note that this test is not valid
* if the path starts with //?/ or \\?\. */
return 0;
}
/* 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).
*/
/* Skip leading drive letter */
pos += 2;
}
else {
/* Is a UNC, so skip the server name and share name */
pos += 2;
pos++;
if (!*pos) {
/* No share name */
return 0;
}
pos++; /* Move to start of share name */
pos++;
if (!*pos) {
/* No path information */
return 0;
}
}
}
while (*pos) {
unsigned int idx;
unsigned int baselength;
pos++;
}
if (*pos == '\0') {
break;
}
pos++;
}
/*
* Now we have a segment of the path, starting at position "segstart"
* and length "seglength"
*/
/* Test 2 */
return 0;
}
}
/* Test 3 */
return 0;
}
/* Test 4 */
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). */
return 0;
}
}
}
}
return 1;
}