home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1999 April / PCO0499.ISO / filesbbs / os2 / apach134.arj / APACH134.ZIP / src / os / win32 / util_win32.c < prev   
Encoding:
C/C++ Source or Header  |  1999-01-05  |  17.5 KB  |  677 lines

  1. #include <windows.h>
  2. #include <sys/stat.h>
  3. #include <stdarg.h>
  4. #include <time.h>
  5. #include <stdlib.h>
  6.  
  7. #include "httpd.h"
  8. #include "http_log.h"
  9.  
  10. /* Returns TRUE if the input string is a string
  11.  * of one or more '.' characters.
  12.  */
  13. static BOOL OnlyDots(char *pString)
  14. {
  15.     char *c;
  16.  
  17.     if (*pString == '\0')
  18.         return FALSE;
  19.  
  20.     for (c = pString;*c;c++)
  21.         if (*c != '.')
  22.             return FALSE;
  23.  
  24.     return TRUE;
  25. }
  26.  
  27. /* Accepts as input a pathname, and tries to match it to an 
  28.  * existing path and return the pathname in the case that
  29.  * is present on the existing path.  This routine also
  30.  * converts alias names to long names.
  31.  */
  32. API_EXPORT(char *) ap_os_systemcase_filename(pool *pPool, 
  33.                                              const char *szFile)
  34. {
  35.     char buf[HUGE_STRING_LEN];
  36.     char *pInputName;
  37.     char *p, *q;
  38.     BOOL bDone = FALSE;
  39.     BOOL bFileExists = TRUE;
  40.     HANDLE hFind;
  41.     WIN32_FIND_DATA wfd;
  42.  
  43.     if (!szFile || strlen(szFile) == 0 || strlen(szFile) >= sizeof(buf))
  44.         return ap_pstrdup(pPool, "");
  45.  
  46.     buf[0] = '\0';
  47.     pInputName = ap_pstrdup(pPool, szFile);
  48.  
  49.     /* First convert all slashes to \ so Win32 calls work OK */
  50.     for (p = pInputName; *p; p++) {
  51.         if (*p == '/')
  52.             *p = '\\';
  53.     }
  54.     
  55.     p = pInputName;
  56.     /* If there is drive information, copy it over. */ 
  57.     if (pInputName[1] == ':') {
  58.         buf[0] = tolower(*p++);
  59.         buf[1] = *p++;
  60.         buf[2] = '\0';
  61.  
  62.         /* If all we have is a drive letter, then we are done */
  63.         if (strlen(pInputName) == 2)
  64.             bDone = TRUE;
  65.     }
  66.     
  67.     q = p;
  68.     if (*p == '\\') {
  69.         p++;
  70.         if (*p == '\\')  /* Possible UNC name */
  71.         {
  72.             p++;
  73.             /* Get past the machine name.  FindFirstFile */
  74.             /* will not find a machine name only */
  75.             p = strchr(p, '\\'); 
  76.             if (p)
  77.             {
  78.                 p++;
  79.                 /* Get past the share name.  FindFirstFile */
  80.                 /* will not find a \\machine\share name only */
  81.                 p = strchr(p, '\\'); 
  82.                 if (p) {
  83.                     strncat(buf,q,p-q);
  84.                     q = p;
  85.                     p++;
  86.                 }
  87.             }
  88.  
  89.             if (!p)
  90.                 p = q;
  91.         }
  92.     }
  93.  
  94.     p = strchr(p, '\\');
  95.  
  96.     while (!bDone) {
  97.         if (p)
  98.             *p = '\0';
  99.  
  100.         if (strchr(q, '*') || strchr(q, '?'))
  101.             bFileExists = FALSE;
  102.  
  103.         /* If the path exists so far, call FindFirstFile
  104.          * again.  However, if this portion of the path contains
  105.          * only '.' charaters, skip the call to FindFirstFile
  106.          * since it will convert '.' and '..' to actual names.
  107.          * Note: in the call to OnlyDots, we may have to skip
  108.          *       a leading slash.
  109.          */
  110.         if (bFileExists && !OnlyDots((*q == '.' ? q : q+1))) {            
  111.             hFind = FindFirstFile(pInputName, &wfd);
  112.             
  113.             if (hFind == INVALID_HANDLE_VALUE) {
  114.                 bFileExists = FALSE;
  115.             }
  116.             else {
  117.                 FindClose(hFind);
  118.  
  119.                 if (*q == '\\')
  120.                     strcat(buf,"\\");
  121.                 strcat(buf, wfd.cFileName);
  122.             }
  123.         }
  124.         
  125.         if (!bFileExists || OnlyDots((*q == '.' ? q : q+1))) {
  126.             strcat(buf, q);
  127.         }
  128.         
  129.         if (p) {
  130.             q = p;
  131.             *p++ = '\\';
  132.             p = strchr(p, '\\');
  133.         }
  134.         else {
  135.             bDone = TRUE;
  136.         }
  137.     }
  138.     
  139.     /* First convert all slashes to / so server code handles it ok */
  140.     for (p = buf; *p; p++) {
  141.         if (*p == '\\')
  142.             *p = '/';
  143.     }
  144.  
  145.     return ap_pstrdup(pPool, buf);
  146. }
  147.  
  148.  
  149. /*  Perform canonicalization with the exception that the
  150.  *  input case is preserved.
  151.  */
  152. API_EXPORT(char *) ap_os_case_canonical_filename(pool *pPool, 
  153.                                                  const char *szFile)
  154. {
  155.     char *pNewStr;
  156.     char *s;
  157.     char *p; 
  158.     char *q;
  159.  
  160.     if (szFile == NULL || strlen(szFile) == 0)
  161.         return ap_pstrdup(pPool, "");
  162.  
  163.     pNewStr = ap_pstrdup(pPool, szFile);
  164.  
  165.     /*  Change all '\' characters to '/' characters.
  166.      *  While doing this, remove any trailing '.'.
  167.      *  Also, blow away any directories with 3 or
  168.      *  more '.'
  169.      */
  170.     for (p = pNewStr,s = pNewStr; *s; s++,p++) {
  171.         if (*s == '\\' || *s == '/') {
  172.  
  173.             q = p;
  174.             while (p > pNewStr && *(p-1) == '.')
  175.                 p--;
  176.  
  177.             if (p == pNewStr && q-p <= 2 && *p == '.')
  178.                 p = q;
  179.             else if (p > pNewStr && p < q && *(p-1) == '/') {
  180.                 if (q-p > 2)
  181.                     p--;
  182.                 else
  183.                     p = q;
  184.             }
  185.  
  186.             *p = '/';
  187.         }
  188.         else {
  189.             *p = *s;
  190.         }
  191.     }
  192.     *p = '\0';
  193.  
  194.     /*  Blow away any final trailing '.' since on Win32
  195.      *  foo.bat == foo.bat. == foo.bat... etc.
  196.      *  Also blow away any trailing spaces since
  197.      *  "filename" == "filename "
  198.      */
  199.     q = p;
  200.     while (p > pNewStr && (*(p-1) == '.' || *(p-1) == ' '))
  201.         p--;
  202.     if ((p > pNewStr) ||
  203.         (p == pNewStr && q-p > 2))
  204.         *p = '\0';
  205.         
  206.  
  207.     /*  One more security issue to deal with.  Win32 allows
  208.      *  you to create long filenames.  However, alias filenames
  209.      *  are always created so that the filename will
  210.      *  conform to 8.3 rules.  According to the Microsoft
  211.      *  Developer's network CD (1/98) 
  212.      *  "Automatically generated aliases are composed of the 
  213.      *   first six characters of the filename plus ~n 
  214.      *   (where n is a number) and the first three characters 
  215.      *   after the last period."
  216.      *  Here, we attempt to detect and decode these names.
  217.      */
  218.     p = strchr(pNewStr, '~');
  219.     if (p != NULL) {
  220.         char *pConvertedName, *pQstr, *pPstr;
  221.         char buf[HUGE_STRING_LEN];
  222.         /* We potentially have a short name.  Call 
  223.          * ap_os_systemcase_filename to examine the filesystem
  224.          * and possibly extract the long name.
  225.          */
  226.         pConvertedName = ap_os_systemcase_filename(pPool, pNewStr);
  227.  
  228.         /* Since we want to preserve the incoming case as much
  229.          * as we can, compare for differences in the string and
  230.          * only substitute in the path names that changed.
  231.          */
  232.         if (stricmp(pNewStr, pConvertedName)) {
  233.             buf[0] = '\0';
  234.  
  235.             q = pQstr = pConvertedName;
  236.             p = pPstr = pNewStr;
  237.             do {
  238.                 q = strchr(q,'/');
  239.                 p = strchr(p,'/');
  240.  
  241.                 if (p != NULL) {
  242.                     *q = '\0';
  243.                     *p = '\0';
  244.                 }
  245.  
  246.                 if (stricmp(pQstr, pPstr)) 
  247.                     strcat(buf, pQstr);   /* Converted name */
  248.                 else 
  249.                     strcat(buf, pPstr);   /* Original name  */
  250.  
  251.  
  252.                 if (p != NULL) {
  253.                     pQstr = q;
  254.                     pPstr = p;
  255.                     *q++ = '/';
  256.                     *p++ = '/';
  257.                 }
  258.  
  259.             } while (p != NULL); 
  260.  
  261.             pNewStr = ap_pstrdup(pPool, buf);
  262.         }
  263.     }
  264.  
  265.  
  266.     return pNewStr;
  267. }
  268.  
  269. /*  Perform complete canonicalization.
  270.  */
  271. API_EXPORT(char *) ap_os_canonical_filename(pool *pPool, const char *szFile)
  272. {
  273.     char *pNewName;
  274.     pNewName = ap_os_case_canonical_filename(pPool, szFile);
  275.     strlwr(pNewName);
  276.     return pNewName;
  277. }
  278.  
  279. /* Win95 doesn't like trailing /s. NT and Unix don't mind. This works 
  280.  * around the problem.
  281.  * Errr... except if it is UNC and we are referring to the root of 
  282.  * the UNC, we MUST have a trailing \ and we can't use /s. Jeez. 
  283.  * Not sure if this refers to all UNCs or just roots,
  284.  * but I'm going to fix it for all cases for now. (Ben)
  285.  */
  286.  
  287. #undef stat
  288. API_EXPORT(int) os_stat(const char *szPath, struct stat *pStat)
  289. {
  290.     int n;
  291.     
  292.     if (strlen(szPath) == 0) {
  293.         return -1;
  294.     }
  295.  
  296.     if (szPath[0] == '/' && szPath[1] == '/') {
  297.     char buf[_MAX_PATH];
  298.     char *s;
  299.     int nSlashes = 0;
  300.  
  301.     ap_assert(strlen(szPath) < _MAX_PATH);
  302.     strcpy(buf, szPath);
  303.     for (s = buf; *s; ++s) {
  304.         if (*s == '/') {
  305.         *s = '\\';
  306.         ++nSlashes;
  307.         }
  308.     }
  309.     /* then we need to add one more to get \\machine\share\ */
  310.     if (nSlashes == 3) {
  311.         *s++ = '\\';
  312.     }
  313.     *s = '\0';
  314.     return stat(buf, pStat);
  315.     }
  316.  
  317.     /*
  318.      * Below removes the trailing /, however, do not remove
  319.      * it in the case of 'x:/' or stat will fail
  320.      */
  321.     n = strlen(szPath);
  322.     if ((szPath[n - 1] == '\\' || szPath[n - 1] == '/') &&
  323.         !(n == 3 && szPath[1] == ':')) {
  324.         char buf[_MAX_PATH];
  325.         
  326.         ap_assert(n < _MAX_PATH);
  327.         strcpy(buf, szPath);
  328.         buf[n - 1] = '\0';
  329.         
  330.         return stat(buf, pStat);
  331.     }
  332.     return stat(szPath, pStat);
  333. }
  334.  
  335. /* Fix two really crap problems with Win32 spawn[lv]e*:
  336.  *
  337.  *  1. Win32 doesn't deal with spaces in argv.
  338.  *  2. Win95 doesn't like / in cmdname.
  339.  */
  340.  
  341. #undef _spawnv
  342. API_EXPORT(int) os_spawnv(int mode, const char *cmdname,
  343.               const char *const *argv)
  344. {
  345.     int n;
  346.     char **aszArgs;
  347.     const char *szArg;
  348.     char *szCmd;
  349.     char *s;
  350.     
  351.     szCmd = _alloca(strlen(cmdname)+1);
  352.     strcpy(szCmd, cmdname);
  353.     for (s = szCmd; *s; ++s) {
  354.         if (*s == '/') {
  355.             *s = '\\';
  356.     }
  357.     }
  358.  
  359.     for (n = 0; argv[n]; ++n)
  360.         ;
  361.  
  362.     aszArgs = _alloca((n + 1) * sizeof(const char *));
  363.  
  364.     for (n = 0; szArg = argv[n]; ++n) {
  365.         if (strchr(szArg, ' ')) {
  366.             int l = strlen(szArg);
  367.  
  368.             aszArgs[n] = _alloca(l + 2 + 1);
  369.             aszArgs[n][0] = '"';
  370.             strcpy(&aszArgs[n][1], szArg);
  371.             aszArgs[n][l + 1] = '"';
  372.             aszArgs[n][l + 2] = '\0';
  373.         }
  374.         else {
  375.             aszArgs[n] = (char *)szArg;
  376.         }
  377.     }
  378.  
  379.     aszArgs[n] = NULL;
  380.  
  381.     return _spawnv(mode, szCmd, aszArgs);
  382. }
  383.  
  384. #undef _spawnve
  385. API_EXPORT(int) os_spawnve(int mode, const char *cmdname,
  386.                const char *const *argv, const char *const *envp)
  387. {
  388.     int n;
  389.     char **aszArgs;
  390.     const char *szArg;
  391.     char *szCmd;
  392.     char *s;
  393.     
  394.     szCmd = _alloca(strlen(cmdname)+1);
  395.     strcpy(szCmd, cmdname);
  396.     for (s = szCmd; *s; ++s) {
  397.         if (*s == '/') {
  398.             *s = '\\';
  399.     }
  400.     }
  401.     
  402.     for (n = 0; argv[n]; ++n)
  403.         ;
  404.  
  405.     aszArgs = _alloca((n + 1)*sizeof(const char *));
  406.  
  407.     for (n = 0; szArg = argv[n]; ++n){
  408.         if (strchr(szArg, ' ')) {
  409.             int l = strlen(szArg);
  410.  
  411.             aszArgs[n] = _alloca(l + 2 + 1);
  412.             aszArgs[n][0] = '"';
  413.             strcpy(&aszArgs[n][1], szArg);
  414.             aszArgs[n][l + 1] = '"';
  415.             aszArgs[n][l + 2] = '\0';
  416.         }
  417.         else {
  418.             aszArgs[n] = (char *)szArg;
  419.         }
  420.     }
  421.  
  422.     aszArgs[n] = NULL;
  423.  
  424.     return _spawnve(mode, szCmd, aszArgs, envp);
  425. }
  426.  
  427. API_EXPORT(int) os_spawnle(int mode, const char *cmdname, ...)
  428. {
  429.     int n;
  430.     va_list vlist;
  431.     char **aszArgs;
  432.     const char *szArg;
  433.     const char *const *aszEnv;
  434.     char *szCmd;
  435.     char *s;
  436.     
  437.     szCmd = _alloca(strlen(cmdname)+1);
  438.     strcpy(szCmd, cmdname);
  439.     for (s = szCmd; *s; ++s) {
  440.         if (*s == '/') {
  441.             *s = '\\';
  442.     }
  443.     }
  444.  
  445.     va_start(vlist, cmdname);
  446.     for (n = 0; va_arg(vlist, const char *); ++n)
  447.         ;
  448.     va_end(vlist);
  449.  
  450.     aszArgs = _alloca((n + 1) * sizeof(const char *));
  451.  
  452.     va_start(vlist, cmdname);
  453.     for (n = 0; szArg = va_arg(vlist, const char *); ++n) {
  454.         if (strchr(szArg, ' ')) {
  455.             int l = strlen(szArg);
  456.  
  457.             aszArgs[n] = _alloca(l + 2 + 1);
  458.             aszArgs[n][0] = '"';
  459.             strcpy(&aszArgs[n][1], szArg);
  460.             aszArgs[n][l + 1] = '"';
  461.             aszArgs[n][l + 2] = '\0';
  462.         }
  463.         else {
  464.             aszArgs[n] = (char *)szArg;
  465.         }
  466.     }
  467.  
  468.     aszArgs[n] = NULL;
  469.  
  470.     aszEnv = va_arg(vlist, const char *const *);
  471.     va_end(vlist);
  472.     
  473.     return _spawnve(mode, szCmd, aszArgs, aszEnv);
  474. }
  475.  
  476. #undef strftime
  477.  
  478. /* Partial replacement for strftime. This adds certain expandos to the
  479.  * Windows version
  480.  */
  481.  
  482. API_EXPORT(int) os_strftime(char *s, size_t max, const char *format,
  483.                             const struct tm *tm) {
  484.    /* If the new format string is bigger than max, the result string probably
  485.     * won't fit anyway. When %-expandos are added, made sure the padding below
  486.     * is enough.
  487.     */
  488.     char *new_format = (char *) _alloca(max + 11);
  489.     size_t i, j, format_length = strlen(format);
  490.     int return_value;
  491.     int length_written;
  492.  
  493.     for (i = 0, j = 0; (i < format_length && j < max);) {
  494.         if (format[i] != '%') {
  495.             new_format[j++] = format[i++];
  496.             continue;
  497.         }
  498.         switch (format[i+1]) {
  499.             case 'D':
  500.                 /* Is this locale dependent? Shouldn't be...
  501.                    Also note the year 2000 exposure here */
  502.                 memcpy(new_format + j, "%m/%d/%y", 8);
  503.                 i += 2;
  504.                 j += 8;
  505.                 break;
  506.             case 'r':
  507.                 memcpy(new_format + j, "%I:%M:%S %p", 11);
  508.                 i += 2;
  509.                 j += 11;
  510.                 break;
  511.             case 'T':
  512.                 memcpy(new_format + j, "%H:%M:%S", 8);
  513.                 i += 2;
  514.                 j += 8;
  515.                 break;
  516.             case 'e':
  517.                 length_written = ap_snprintf(new_format + j, max - j, "%2d",
  518.                     tm->tm_mday);
  519.                 j = (length_written == -1) ? max : (j + length_written);
  520.                 i += 2;
  521.                 break;
  522.             default:
  523.                 /* We know we can advance two characters forward here. */
  524.                 new_format[j++] = format[i++];
  525.                 new_format[j++] = format[i++];
  526.         }
  527.     }
  528.     if (j >= max) {
  529.         *s = '\0';  /* Defensive programming, okay since output is undefined */
  530.         return_value = 0;
  531.     } else {
  532.         new_format[j] = '\0';
  533.         return_value = strftime(s, max, new_format, tm);
  534.     }
  535.     return return_value;
  536. }
  537.  
  538. /*
  539.  * ap_os_is_filename_valid is given a filename, and returns 0 if the filename
  540.  * is not valid for use on this system. On Windows, this means it fails any
  541.  * of the tests below. Otherwise returns 1.
  542.  *
  543.  * Test for filename validity on Win32. This is of tests come in part from
  544.  * the MSDN article at "Technical Articles, Windows Platform, Base Services,
  545.  * Guidelines, Making Room for Long Filenames" although the information
  546.  * in MSDN about filename testing is incomplete or conflicting. There is a
  547.  * similar set of tests in "Technical Articles, Windows Platform, Base Services,
  548.  * Guidelines, Moving Unix Applications to Windows NT".
  549.  *
  550.  * The tests are:
  551.  *
  552.  * 1) total path length greater than MAX_PATH
  553.  *
  554.  * 2) anything using the octets 0-31 or characters " < > | :
  555.  *    (these are reserved for Windows use in filenames. In addition
  556.  *     each file system has its own additional characters that are
  557.  *     invalid. See KB article Q100108 for more details).
  558.  *
  559.  * 3) anything ending in "." (no matter how many)
  560.  *    (filename doc, doc. and doc... all refer to the same file)
  561.  *
  562.  * 4) any segment in which the basename (before first period) matches
  563.  *    one of the DOS device names
  564.  *    (the list comes from KB article Q100108 although some people
  565.  *     reports that additional names such as "COM5" are also special
  566.  *     devices).
  567.  *
  568.  * If the path fails ANY of these tests, the result must be to deny access.
  569.  */
  570.  
  571. API_EXPORT(int) ap_os_is_filename_valid(const char *file)
  572. {
  573.     const char *segstart;
  574.     char seglength;
  575.     const char *pos;
  576.     static const char * const invalid_characters = "?\"<>*|:";
  577.     static const char * const invalid_filenames[] = { 
  578.     "CON", "AUX", "COM1", "COM2", "COM3", 
  579.     "COM4", "LPT1", "LPT2", "LPT3", "PRN", "NUL", NULL 
  580.     };
  581.  
  582.     /* Test 1 */
  583.     if (strlen(file) > MAX_PATH) {
  584.     /* Path too long for Windows. Note that this test is not valid
  585.      * if the path starts with //?/ or \\?\. */
  586.     return 0;
  587.     }
  588.  
  589.     pos = file;
  590.  
  591.     /* Skip any leading non-path components. This can be either a
  592.      * drive letter such as C:, or a UNC path such as \\SERVER\SHARE\.
  593.      * We continue and check the rest of the path based on the rules above.
  594.      * This means we could eliminate valid filenames from servers which
  595.      * are not running NT (such as Samba).
  596.      */
  597.  
  598.     if (pos[0] && pos[1] == ':') {
  599.     /* Skip leading drive letter */
  600.     pos += 2;
  601.     }
  602.     else {
  603.     if ((pos[0] == '\\' || pos[0] == '/') &&
  604.         (pos[1] == '\\' || pos[1] == '/')) {
  605.         /* Is a UNC, so skip the server name and share name */
  606.         pos += 2;
  607.         while (*pos && *pos != '/' && *pos != '\\')
  608.         pos++;
  609.         if (!*pos) {
  610.         /* No share name */
  611.         return 0;
  612.         }
  613.         pos++;    /* Move to start of share name */
  614.         while (*pos && *pos != '/' && *pos != '\\')
  615.         pos++;
  616.         if (!*pos) {
  617.         /* No path information */
  618.         return 0;
  619.         }
  620.     }
  621.     }
  622.  
  623.     while (*pos) {
  624.     int idx;
  625.     int baselength;
  626.  
  627.     while (*pos == '/' || *pos == '\\') {
  628.             pos++;
  629.     }
  630.     if (*pos == '\0') {
  631.         break;
  632.     }
  633.     segstart = pos;    /* start of segment */
  634.     while (*pos && *pos != '/' && *pos != '\\') {
  635.         pos++;
  636.     }
  637.     seglength = pos - segstart;
  638.     /* 
  639.      * Now we have a segment of the path, starting at position "segstart"
  640.      * and length "seglength"
  641.      */
  642.  
  643.     /* Test 2 */
  644.     for (idx = 0; idx < seglength; idx++) {
  645.         if (segstart[idx] < 32 ||
  646.         strchr(invalid_characters, segstart[idx])) {
  647.         return 0;
  648.         }
  649.     }
  650.  
  651.     /* Test 3 */
  652.     if (segstart[seglength-1] == '.') {
  653.         return 0;
  654.     }
  655.  
  656.     /* Test 4 */
  657.     for (baselength = 0; baselength < seglength; baselength++) {
  658.         if (segstart[baselength] == '.') {
  659.         break;
  660.         }
  661.     }
  662.  
  663.     /* baselength is the number of characters in the base path of
  664.      * the segment (which could be the same as the whole segment length,
  665.      * if it does not include any dot characters). */
  666.     if (baselength == 3 || baselength == 4) {
  667.         for (idx = 0; invalid_filenames[idx]; idx++) {
  668.         if (!strnicmp(invalid_filenames[idx], segstart, baselength)) {
  669.             return 0;
  670.         }
  671.         }
  672.     }
  673.     }
  674.  
  675.     return 1;
  676. }
  677.