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;
  
  
  

Reply via email to