home *** CD-ROM | disk | FTP | other *** search
/ Big Green CD 8 / BGCD_8_Dev.iso / NEXTSTEP / UNIX / Web / Servers / apache-1.2.4-MIHS / original-source / src / util.c < prev   
Encoding:
C/C++ Source or Header  |  1997-06-26  |  31.7 KB  |  1,356 lines

  1. /* ====================================================================
  2.  * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer. 
  10.  *
  11.  * 2. Redistributions in binary form must reproduce the above copyright
  12.  *    notice, this list of conditions and the following disclaimer in
  13.  *    the documentation and/or other materials provided with the
  14.  *    distribution.
  15.  *
  16.  * 3. All advertising materials mentioning features or use of this
  17.  *    software must display the following acknowledgment:
  18.  *    "This product includes software developed by the Apache Group
  19.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  20.  *
  21.  * 4. The names "Apache Server" and "Apache Group" must not be used to
  22.  *    endorse or promote products derived from this software without
  23.  *    prior written permission.
  24.  *
  25.  * 5. Redistributions of any form whatsoever must retain the following
  26.  *    acknowledgment:
  27.  *    "This product includes software developed by the Apache Group
  28.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  29.  *
  30.  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
  31.  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  32.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  33.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
  34.  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  38.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  41.  * OF THE POSSIBILITY OF SUCH DAMAGE.
  42.  * ====================================================================
  43.  *
  44.  * This software consists of voluntary contributions made by many
  45.  * individuals on behalf of the Apache Group and was originally based
  46.  * on public domain software written at the National Center for
  47.  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
  48.  * For more information on the Apache Group and the Apache HTTP server
  49.  * project, please see <http://www.apache.org/>.
  50.  *
  51.  */
  52.  
  53. /*
  54.  * util.c: string utility things
  55.  * 
  56.  * 3/21/93 Rob McCool
  57.  * 1995-96 Many changes by the Apache Group
  58.  * 
  59.  */
  60.  
  61. #include "httpd.h"
  62. #include "http_conf_globals.h"    /* for user_id & group_id */
  63.  
  64. const char month_snames[12][4] = {
  65.     "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
  66. };
  67.  
  68. char *get_time() {
  69.     time_t t;
  70.     char *time_string;
  71.  
  72.     t=time(NULL);
  73.     time_string = ctime(&t);
  74.     time_string[strlen(time_string) - 1] = '\0';
  75.     return (time_string);
  76. }
  77.  
  78. char *ht_time(pool *p, time_t t, const char *fmt, int gmt) {
  79.     char ts[MAX_STRING_LEN];
  80.     struct tm *tms;
  81.  
  82.     tms = (gmt ? gmtime(&t) : localtime(&t));
  83.  
  84.     /* check return code? */
  85.     strftime(ts,MAX_STRING_LEN,fmt,tms);
  86.     return pstrdup (p, ts);
  87. }
  88.  
  89. char *gm_timestr_822(pool *p, time_t sec) {
  90.     static const char *const days[7]=
  91.        {"Sun","Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  92.     char ts[50];
  93.     struct tm *tms;
  94.  
  95.     tms = gmtime(&sec);
  96.  
  97. /* RFC date format; as strftime '%a, %d %b %Y %T GMT' */
  98.     ap_snprintf(ts, sizeof(ts), 
  99.         "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", days[tms->tm_wday],
  100.         tms->tm_mday, month_snames[tms->tm_mon], tms->tm_year + 1900,
  101.         tms->tm_hour, tms->tm_min, tms->tm_sec);
  102.  
  103.     return pstrdup (p, ts);
  104. }
  105.  
  106. /* What a pain in the ass. */
  107. #if defined(HAVE_GMTOFF)
  108. struct tm *get_gmtoff(int *tz) {
  109.     time_t tt = time(NULL);
  110.     struct tm *t;
  111.  
  112.     t = localtime(&tt);
  113.     *tz = (int) (t->tm_gmtoff / 60);
  114.     return t;
  115. }
  116. #else
  117. struct tm *get_gmtoff(int *tz) {
  118.     time_t tt = time(NULL);
  119.     struct tm gmt;
  120.     struct tm *t;
  121.     int days, hours, minutes;
  122.  
  123.     /* Assume we are never more than 24 hours away. */
  124.     gmt = *gmtime(&tt); /* remember gmtime/localtime return ptr to static */
  125.     t = localtime(&tt); /* buffer... so be careful */
  126.     days = t->tm_yday - gmt.tm_yday;
  127.     hours = ((days < -1 ? 24 : 1 < days ? -24 : days * 24)
  128.          + t->tm_hour - gmt.tm_hour);
  129.     minutes = hours * 60 + t->tm_min - gmt.tm_min;
  130.     *tz = minutes;
  131.     return t;
  132. }
  133. #endif
  134.  
  135.  
  136. /* Match = 0, NoMatch = 1, Abort = -1 */
  137. /* Based loosely on sections of wildmat.c by Rich Salz
  138.  * Hmmm... shouldn't this really go component by component?
  139.  */
  140. int strcmp_match(const char *str, const char *exp) {
  141.     int x,y;
  142.  
  143.     for(x=0,y=0;exp[y];++y,++x) {
  144.         if((!str[x]) && (exp[y] != '*'))
  145.             return -1;
  146.         if(exp[y] == '*') {
  147.             while(exp[++y] == '*');
  148.             if(!exp[y])
  149.                 return 0;
  150.             while(str[x]) {
  151.                 int ret;
  152.                 if((ret = strcmp_match(&str[x++],&exp[y])) != 1)
  153.                     return ret;
  154.             }
  155.             return -1;
  156.         } else 
  157.             if((exp[y] != '?') && (str[x] != exp[y]))
  158.                 return 1;
  159.     }
  160.     return (str[x] != '\0');
  161. }
  162.  
  163. int strcasecmp_match(const char *str, const char *exp) {
  164.     int x,y;
  165.  
  166.     for(x=0,y=0;exp[y];++y,++x) {
  167.         if((!str[x]) && (exp[y] != '*'))
  168.             return -1;
  169.         if(exp[y] == '*') {
  170.             while(exp[++y] == '*');
  171.             if(!exp[y])
  172.                 return 0;
  173.             while(str[x]) {
  174.                 int ret;
  175.                 if((ret = strcasecmp_match(&str[x++],&exp[y])) != 1)
  176.                     return ret;
  177.             }
  178.             return -1;
  179.         } else 
  180.             if((exp[y] != '?') && (tolower(str[x]) != tolower(exp[y])))
  181.                 return 1;
  182.     }
  183.     return (str[x] != '\0');
  184. }
  185.  
  186. int is_matchexp(const char *str) {
  187.     register int x;
  188.  
  189.     for(x=0;str[x];x++)
  190.         if((str[x] == '*') || (str[x] == '?'))
  191.             return 1;
  192.     return 0;
  193. }
  194.  
  195. /* This function substitutes for $0-$9, filling in regular expression
  196.  * submatches. Pass it the same nmatch and pmatch arguments that you
  197.  * passed regexec(). pmatch should not be greater than the maximum number
  198.  * of subexpressions - i.e. one more than the re_nsub member of regex_t.
  199.  *
  200.  * input should be the string with the $-expressions, source should be the
  201.  * string that was matched against.
  202.  *
  203.  * It returns the substituted string, or NULL on error.
  204.  *
  205.  * Parts of this code are based on Henry Spencer's regsub(), from his
  206.  * AT&T V8 regexp package.
  207.  */
  208.  
  209. char *pregsub(pool *p, const char *input, const char *source,
  210.           size_t nmatch, regmatch_t pmatch[]) {
  211.     const char *src = input;
  212.     char *dest, *dst;
  213.     char c;
  214.     int no, len;
  215.  
  216.     if (!source) return NULL;
  217.     if (!nmatch) return pstrdup(p, src);
  218.  
  219.     /* First pass, find the size */
  220.  
  221.     len = 0;
  222.  
  223.     while ((c = *src++) != '\0') {
  224.     if (c == '&')
  225.         no = 0;
  226.     else if (c == '$' && isdigit(*src))
  227.         no = *src++ - '0';
  228.     else
  229.         no = -1;
  230.     
  231.     if (no < 0) {   /* Ordinary character. */
  232.         if (c == '\\' && (*src == '$' || *src == '&'))
  233.         c = *src++;
  234.         len++;
  235.     } else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
  236.         len += pmatch[no].rm_eo - pmatch[no].rm_so;
  237.     }
  238.  
  239.     }
  240.  
  241.     dest = dst = pcalloc(p, len + 1);
  242.  
  243.     /* Now actually fill in the string */
  244.  
  245.     src = input;
  246.  
  247.     while ((c = *src++) != '\0') {
  248.     if (c == '&')
  249.         no = 0;
  250.     else if (c == '$' && isdigit(*src))
  251.         no = *src++ - '0';
  252.     else
  253.         no = -1;
  254.     
  255.     if (no < 0) {   /* Ordinary character. */
  256.         if (c == '\\' && (*src == '$' || *src == '&'))
  257.         c = *src++;
  258.         *dst++ = c;
  259.     } else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
  260.         len = pmatch[no].rm_eo - pmatch[no].rm_so;
  261.         strncpy(dst, source + pmatch[no].rm_so, len);
  262.         dst += len;
  263.         if (*(dst-1) == '\0') /* strncpy hit NULL. */
  264.         return NULL;
  265.     }
  266.  
  267.     }
  268.     *dst = '\0';
  269.     
  270.     return dest;
  271. }
  272.  
  273. /*
  274.  * Parse .. so we don't compromise security
  275.  */
  276. void getparents(char *name)
  277. {
  278.     int l, w;
  279.  
  280.     /* Four paseses, as per RFC 1808 */
  281.     /* a) remove ./ path segments */
  282.  
  283.     for (l=0, w=0; name[l] != '\0';)
  284.     {
  285.     if (name[l] == '.' && name[l+1] == '/' && (l == 0 || name[l-1] == '/'))
  286.         l += 2;
  287.     else
  288.         name[w++] = name[l++];
  289.     }
  290.  
  291.     /* b) remove trailing . path, segment */
  292.     if (w == 1 && name[0] == '.') w--;
  293.     else if (w > 1 && name[w-1] == '.' && name[w-2] == '/') w--;
  294.     name[w] = '\0';
  295.  
  296.     /* c) remove all xx/../ segments. (including leading ../ and /../) */
  297.     l = 0;
  298.  
  299.     while(name[l]!='\0') {
  300.         if(name[l] == '.' && name[l+1] == '.' && name[l+2] == '/' &&
  301.         (l == 0 || name[l-1] == '/')) {
  302.         register int m=l+3,n;
  303.  
  304.         l=l-2;
  305.         if(l>=0) {
  306.             while(l >= 0 && name[l] != '/') l--;
  307.             l++;
  308.         }
  309.         else l=0;
  310.         n=l;
  311.         while((name[n]=name[m])) (++n,++m);
  312.             }
  313.     else ++l;
  314.     }
  315.  
  316.     /* d) remove trailing xx/.. segment. */
  317.     if (l == 2 && name[0] == '.' && name[1] == '.') name[0] = '\0';
  318.     else if (l > 2 && name[l-1] == '.' && name[l-2] == '.' && name[l-3] == '/')
  319.     {
  320.     l = l - 4;
  321.     if (l >= 0)
  322.     {
  323.         while (l >= 0 && name[l] != '/') l--;
  324.         l++;
  325.     }
  326.     else l = 0;
  327.     name[l] = '\0';
  328.     }
  329.  
  330. void no2slash(char *name) {
  331.     register int x,y;
  332.  
  333.     for(x=0; name[x];)
  334.         if(x && (name[x-1] == '/') && (name[x] == '/'))
  335.             for(y=x+1;name[y-1];y++)
  336.                 name[y-1] = name[y];
  337.     else x++;
  338. }
  339.  
  340. char *make_dirstr(pool *p, const char *s, int n) {
  341.     register int x,f;
  342.     char *res;
  343.  
  344.     for(x=0,f=0;s[x];x++) {
  345.         if(s[x] == '/')
  346.             if((++f) == n) {
  347.         res = palloc(p, x + 2);
  348.         strncpy (res, s, x);
  349.         res[x] = '/';
  350.         res[x+1] = '\0';
  351.                 return res;
  352.             }
  353.     }
  354.  
  355.     if (s[strlen(s) - 1] == '/')
  356.         return pstrdup (p, s);
  357.     else
  358.         return pstrcat (p, s, "/", NULL);
  359. }
  360.  
  361. int count_dirs(const char *path) {
  362.     register int x,n;
  363.  
  364.     for(x=0,n=0;path[x];x++)
  365.         if(path[x] == '/') n++;
  366.     return n;
  367. }
  368.  
  369.  
  370. void chdir_file(const char *file) {
  371.     int i;
  372.  
  373.     if((i = rind(file,'/')) == -1)
  374.         return;
  375.     ((char *)file)[i] = '\0';
  376.     chdir(file);
  377.     ((char *)file)[i] = '/';
  378. }
  379.  
  380. char *getword_nc(pool* atrans, char **line, char stop)
  381.     {
  382.     return getword(atrans,(const char **)line,stop);
  383.     }
  384.  
  385. char *getword(pool* atrans, const char **line, char stop) {
  386.     int pos = ind(*line, stop);
  387.     char *res;
  388.  
  389.     if (pos == -1) {
  390.         res = pstrdup (atrans, *line);
  391.     *line += strlen (*line);
  392.     return res;
  393.     }
  394.   
  395.     res = palloc(atrans, pos + 1);
  396.     strncpy (res, *line, pos);
  397.     res[pos] = '\0';
  398.     
  399.     while ((*line)[pos] == stop) ++pos;
  400.     
  401.     *line += pos;
  402.     
  403.     return res;
  404. }
  405.  
  406. char *getword_white_nc(pool* atrans, char **line)
  407. {
  408.     return getword_white(atrans,(const char **)line);
  409. }
  410.  
  411. char *getword_white(pool* atrans, const char **line) {
  412.     int pos = -1, x;
  413.     char *res;
  414.  
  415.     for(x=0;(*line)[x];x++) {
  416.         if (isspace((*line)[x])) {
  417.           pos=x;
  418.           break;
  419.       }
  420.    }
  421.  
  422.     if (pos == -1) {
  423.         res = pstrdup (atrans, *line);
  424.       *line += strlen (*line);
  425.       return res;
  426.     }
  427.  
  428.     res = palloc(atrans, pos + 1);
  429.     strncpy (res, *line, pos);
  430.     res[pos] = '\0';
  431.  
  432.     while (isspace((*line)[pos])) ++pos;
  433.  
  434.     *line += pos;
  435.  
  436.     return res;
  437. }
  438.  
  439. char *getword_nulls_nc(pool* atrans, char **line, char stop)
  440. {
  441.     return getword_nulls(atrans,(const char **)line,stop);
  442. }
  443.  
  444. char *getword_nulls(pool* atrans, const char **line, char stop) {
  445.     int pos = ind(*line, stop);
  446.     char *res;
  447.  
  448.     if (pos == -1) {
  449.         res = pstrdup (atrans, *line);
  450.     *line += strlen (*line);
  451.     return res;
  452.     }
  453.   
  454.     res = palloc(atrans, pos + 1);
  455.     strncpy (res, *line, pos);
  456.     res[pos] = '\0';
  457.     
  458.     ++pos;
  459.     
  460.     *line += pos;
  461.     
  462.     return res;
  463. }
  464.  
  465. /* Get a word, (new) config-file style --- quoted strings and backslashes
  466.  * all honored
  467.  */
  468.  
  469. static char *substring_conf (pool *p, const char *start, int len, char quote)
  470. {
  471.     char *result = palloc (p, len + 2);
  472.     char *resp = result;
  473.     int i;
  474.  
  475.     for (i = 0; i < len; ++i) {
  476.         if (start[i] == '\\' && (start[i+1] == '/'
  477.                  || (quote && start[i+1] == quote)))
  478.         *resp++ = start[++i];
  479.     else
  480.         *resp++ = start[i];
  481.     }
  482.  
  483.     *resp++ = '\0';
  484.     return result;
  485. }
  486.  
  487. char *getword_conf_nc(pool* p, char **line) {
  488.     return getword_conf(p,(const char **)line);
  489. }
  490.  
  491. char *getword_conf(pool* p, const char **line) {
  492.     const char *str = *line, *strend;
  493.     char *res;
  494.     char quote;
  495.  
  496.     while (*str && isspace (*str))
  497.         ++str;
  498.  
  499.     if (!*str) {
  500.         *line = str;
  501.         return "";
  502.     }
  503.  
  504.     if ((quote = *str) == '"' || quote == '\'') {
  505.         strend = str + 1;
  506.     while (*strend && *strend != quote) {
  507.         if (*strend == '\\' && strend[1] && strend[1] == quote)
  508.         strend += 2;
  509.         else ++strend;
  510.     }
  511.     res = substring_conf (p, str + 1, strend - str - 1, quote);
  512.  
  513.     if (*strend == quote) ++strend;
  514.     } else {
  515.         strend = str;
  516.     while (*strend && !isspace (*strend))
  517.         ++strend;
  518.  
  519.     res = substring_conf (p, str, strend - str, 0);
  520.     }
  521.  
  522.     while (*strend && isspace(*strend)) ++ strend;
  523.     *line = strend;
  524.     return res;
  525. }
  526.  
  527. #ifdef UNDEF
  528. /* this function is dangerous, and superceded by getword_white, so don't use it
  529.  */
  530. void cfg_getword(char *word, char *line) {
  531.     int x=0,y;
  532.     
  533.     for(x=0;line[x] && isspace(line[x]);x++);
  534.     y=0;
  535.     while(1) {
  536.         if(!(word[y] = line[x]))
  537.             break;
  538.         if(isspace(line[x]))
  539.             if((!x) || (line[x-1] != '\\'))
  540.                 break;
  541.         if(line[x] != '\\') ++y;
  542.         ++x;
  543.     }
  544.     word[y] = '\0';
  545.     while(line[x] && isspace(line[x])) ++x;
  546.     for(y=0;(line[y] = line[x]);++x,++y);
  547. }
  548. #endif
  549.  
  550. int
  551. cfg_getline(char *s, int n, FILE *f) {
  552.     register int i=0, c;
  553.  
  554.     s[0] = '\0';
  555.     /* skip leading whitespace */
  556.     do {
  557.         c = getc(f);
  558.     } while (c == '\t' || c == ' ');
  559.  
  560.     if(c == EOF)
  561.     return 1;
  562.  
  563.     while(1) {
  564.         if((c == '\t') || (c == ' ')) {
  565.             s[i++] = ' ';
  566.             while((c == '\t') || (c == ' ')) 
  567.                 c = getc(f);
  568.         }
  569.         if(c == CR) {
  570.             c = getc(f);
  571.         }
  572.         if(c == EOF || c == 0x4 || c == LF || i == (n-1)) {
  573.             /* blast trailing whitespace */
  574.             while(i && (s[i-1] == ' ')) --i;
  575.             s[i] = '\0';
  576.         return 0;
  577.         }
  578.         s[i] = c;
  579.         ++i;
  580.         c = getc(f);
  581.     }
  582. }
  583.  
  584. /* Retrieve a token, spacing over it and returning a pointer to
  585.  * the first non-white byte afterwards.  Note that these tokens
  586.  * are delimited by semis and commas; and can also be delimited
  587.  * by whitespace at the caller's option.
  588.  */
  589.  
  590. char *get_token (pool *p, char **accept_line, int accept_white)
  591. {
  592.     char *ptr = *accept_line;
  593.     char *tok_start;
  594.     char *token;
  595.     int tok_len;
  596.   
  597.     /* Find first non-white byte */
  598.     
  599.     while (*ptr && isspace(*ptr))
  600.       ++ptr;
  601.  
  602.     tok_start = ptr;
  603.     
  604.     /* find token end, skipping over quoted strings.
  605.      * (comments are already gone).
  606.      */
  607.     
  608.     while (*ptr && (accept_white || !isspace(*ptr))
  609.        && *ptr != ';' && *ptr != ',')
  610.     {
  611.     if (*ptr++ == '"')
  612.         while (*ptr)
  613.             if (*ptr++ == '"') break;
  614.     }
  615.       
  616.     tok_len = ptr - tok_start;
  617.     token = palloc (p, tok_len + 1);
  618.     strncpy (token, tok_start, tok_len);
  619.     token[tok_len] = '\0';
  620.     
  621.     /* Advance accept_line pointer to the next non-white byte */
  622.  
  623.     while (*ptr && isspace(*ptr))
  624.       ++ptr;
  625.  
  626.     *accept_line = ptr;
  627.     return token;
  628. }
  629.  
  630. static char* tspecials = " \t()<>@,;:\\/[]?={}";
  631.  
  632. /* Next HTTP token from a header line.  Warning --- destructive!
  633.  * Use only with a copy!
  634.  */
  635.  
  636. static char *next_token (char **toks) {
  637.     char *cp = *toks;
  638.     char *ret;
  639.  
  640.     while (*cp && (iscntrl (*cp) || strchr (tspecials, *cp))) {
  641.         if (*cp == '"')
  642.       while (*cp && (*cp != '"')) ++cp;
  643.     else
  644.       ++cp;
  645.     }
  646.  
  647.     if (!*cp) ret = NULL;
  648.     else {
  649.         ret = cp;
  650.  
  651.         while (*cp && !iscntrl(*cp) && !strchr (tspecials, *cp))
  652.             ++cp;
  653.  
  654.         if (*cp) {
  655.             *toks = cp + 1;
  656.             *cp = '\0';
  657.     }
  658.         else *toks = cp;
  659.     }
  660.  
  661.     return ret;
  662. }
  663.  
  664. int find_token (pool *p, const char *line, const char *tok) {
  665.     char *ltok;
  666.     char *lcopy;
  667.  
  668.     if (!line) return 0;
  669.  
  670.     lcopy = pstrdup (p, line);
  671.     while ((ltok = next_token (&lcopy)))
  672.         if (!strcasecmp (ltok, tok))
  673.             return 1;
  674.  
  675.     return 0;
  676. }
  677.  
  678. int find_last_token (pool *p, const char *line, const char *tok)
  679. {
  680.     int llen, tlen, lidx;
  681.  
  682.     if (!line) return 0;
  683.  
  684.     llen = strlen(line);
  685.     tlen = strlen(tok);
  686.     lidx = llen - tlen;
  687.  
  688.     if ((lidx < 0) ||
  689.         ((lidx > 0) && !(isspace(line[lidx-1]) || line[lidx-1] == ',')))
  690.         return 0;
  691.  
  692.     return (strncasecmp(&line[lidx], tok, tlen) == 0);
  693. }
  694.  
  695. char *escape_shell_cmd(pool *p, const char *s) {
  696.     register int x,y,l;
  697.     char *cmd;
  698.  
  699.     l=strlen(s);
  700.     cmd = palloc (p, 2 * l + 1); /* Be safe */
  701.     strcpy (cmd, s);
  702.     
  703.     for(x=0;cmd[x];x++) {
  704.     
  705. #ifdef __EMX__
  706.         /* Don't allow '&' in parameters under OS/2. */
  707.         /* This can be used to send commands to the shell. */
  708.         if (cmd[x] == '&') {
  709.             cmd[x] = ' ';
  710.         }
  711. #endif
  712.  
  713.         if(ind("&;`'\"|*?~<>^()[]{}$\\\n",cmd[x]) != -1){
  714.             for(y=l+1;y>x;y--)
  715.                 cmd[y] = cmd[y-1];
  716.             l++; /* length has been increased */
  717.             cmd[x] = '\\';
  718.             x++; /* skip the character */
  719.         }
  720.     }
  721.  
  722.     return cmd;
  723. }
  724.  
  725. void plustospace(char *str) {
  726.     register int x;
  727.  
  728.     for(x=0;str[x];x++) if(str[x] == '+') str[x] = ' ';
  729. }
  730.  
  731. void spacetoplus(char *str) {
  732.     register int x;
  733.  
  734.     for(x=0;str[x];x++) if(str[x] == ' ') str[x] = '+';
  735. }
  736.  
  737. static char x2c(const char *what) {
  738.     register char digit;
  739.  
  740.     digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
  741.     digit *= 16;
  742.     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
  743.     return(digit);
  744. }
  745.  
  746. /*
  747.  * Unescapes a URL.
  748.  * Returns 0 on success, non-zero on error
  749.  * Failure is due to
  750.  *   bad % escape       returns BAD_REQUEST
  751.  *
  752.  *   decoding %00 -> \0
  753.  *   decoding %2f -> /   (a special character)
  754.  *                      returns NOT_FOUND
  755.  */
  756. int
  757. unescape_url(char *url) {
  758.     register int x,y, badesc, badpath;
  759.  
  760.     badesc = 0;
  761.     badpath = 0;
  762.     for(x=0,y=0;url[y];++x,++y) {
  763.     if (url[y] != '%') url[x] = url[y];
  764.     else
  765.     {
  766.         if (!isxdigit(url[y+1]) || !isxdigit(url[y+2]))
  767.         {
  768.         badesc = 1;
  769.         url[x] = '%';
  770.         } else
  771.         {
  772.         url[x] = x2c(&url[y+1]);
  773.         y += 2;
  774.         if (url[x] == '/' || url[x] == '\0') badpath = 1;
  775.         }
  776.         }
  777.     }
  778.     url[x] = '\0';
  779.     if (badesc) return BAD_REQUEST;
  780.     else if (badpath) return NOT_FOUND;
  781.     else return OK;
  782. }
  783.  
  784. char *construct_server(pool *p, const char *hostname, unsigned port) {
  785.     char portnum[22];        
  786.     /* Long enough, even if port > 16 bits for some reason */
  787.   
  788.     if (port == DEFAULT_PORT)
  789.     return (char *)hostname;
  790.     else {
  791.         ap_snprintf (portnum, sizeof(portnum), "%u", port);
  792.     return pstrcat (p, hostname, ":", portnum, NULL);
  793.     }
  794. }
  795.  
  796. char *construct_url(pool *p, const char *uri, const server_rec *s) {
  797.     return pstrcat (p, "http://",
  798.             construct_server(p, s->server_hostname, s->port),
  799.             uri, NULL);
  800. }
  801.  
  802. #define c2x(what,where) sprintf(where,"%%%02x",(unsigned char)what)
  803.  
  804. /*
  805. escape_path_segment() escapes a path segment, as defined in RFC 1808. This
  806. routine is (should be) OS independent.
  807.  
  808. os_escape_path() converts an OS path to a URL, in an OS dependent way. In all
  809. cases if a ':' occurs before the first '/' in the URL, the URL should be
  810. prefixed with "./" (or the ':' escaped). In the case of Unix, this means
  811. leaving '/' alone, but otherwise doing what escape_path_segment() does. For
  812. efficiency reasons, we don't use escape_path_segment(), which is provided for
  813. reference. Again, RFC 1808 is where this stuff is defined.
  814.  
  815. If partial is set, os_escape_path() assumes that the path will be appended to
  816. something with a '/' in it (and thus does not prefix "./").
  817. */
  818.  
  819. char *escape_path_segment(pool *p, const char *segment) {
  820.     register int x,y;
  821.     char *copy = palloc (p, 3 * strlen (segment) + 1);
  822.             
  823.     for(x=0,y=0; segment[x]; x++,y++) {
  824.       char c=segment[x];
  825.       if((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c >'9')
  826.      && ind("$-_.+!*'(),:@&=~",c) == -1)
  827.     {
  828.       c2x(c,©[y]);
  829.       y+=2;
  830.     }
  831.       else
  832.     copy[y]=c;
  833.     }
  834.     copy[y] = '\0';
  835.     return copy;
  836. }
  837.  
  838. char *os_escape_path(pool *p,const char *path,int partial) {
  839.   char *copy=palloc(p,3*strlen(path)+3);
  840.   char *s=copy;
  841.  
  842.   if(!partial)
  843.     {
  844.       int colon=ind(path,':');
  845.       int slash=ind(path,'/');
  846.  
  847.       if(colon >= 0 && (colon < slash || slash < 0))
  848.     {
  849.       *s++='.';
  850.       *s++='/';
  851.     }
  852.     }
  853.   for( ; *path ; ++path)
  854.     {
  855.       char c=*path;
  856.       if((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c >'9')
  857.      && ind("$-_.+!*'(),:@&=/~",c) == -1)
  858.     {
  859.       c2x(c,s);
  860.       s+=3;
  861.     }
  862.       else
  863.     *s++=c;
  864.     }
  865.   *s='\0';
  866.   return copy;
  867. }
  868.  
  869. /* escape_uri is now a macro for os_escape_path */
  870.  
  871. char *escape_html(pool *p, const char *s)
  872. {
  873.     int i, j;
  874.     char *x;
  875.  
  876. /* first, count the number of extra characters */
  877.     for (i=0, j=0; s[i] != '\0'; i++)
  878.     if (s[i] == '<' || s[i] == '>') j += 3;
  879.     else if (s[i] == '&') j += 4;
  880.  
  881.     if (j == 0) return pstrdup(p, s);
  882.     x = palloc(p, i + j + 1);
  883.     for (i=0, j=0; s[i] != '\0'; i++, j++)
  884.     if (s[i] == '<')
  885.     {
  886.         memcpy(&x[j], "<", 4);
  887.         j += 3;
  888.     } else if (s[i] == '>')
  889.     {
  890.         memcpy(&x[j], ">", 4);
  891.         j += 3;
  892.     } else if (s[i] == '&')
  893.     {
  894.         memcpy(&x[j], "&", 5);
  895.         j += 4;
  896.     } else
  897.             x[j] = s[i];
  898.  
  899.     x[j] = '\0';
  900.     return x;
  901. }
  902.  
  903. int is_directory(const char *path) {
  904.     struct stat finfo;
  905.  
  906.     if(stat(path,&finfo) == -1)
  907.         return 0; /* in error condition, just return no */
  908.  
  909.     return(S_ISDIR(finfo.st_mode));
  910. }
  911.  
  912. char *make_full_path(pool *a, const char *src1, const char *src2) {
  913.     register int x;
  914.  
  915.     x = strlen(src1);
  916.     if (x == 0) return pstrcat (a, "/", src2, NULL);
  917.  
  918.     if (src1[x - 1] != '/') return pstrcat (a, src1, "/", src2, NULL);
  919.     else return pstrcat (a, src1, src2, NULL);
  920. }
  921.  
  922. /*
  923.  * Check for an absoluteURI syntax (see section 3.2 in RFC2068).
  924.  */
  925. int is_url(const char *u) {
  926.     register int x;
  927.  
  928.     for (x = 0; u[x] != ':'; x++) {
  929.         if ((! u[x]) ||
  930.         ((! isalpha(u[x])) && (! isdigit(u[x])) &&
  931.          (u[x] != '+') && (u[x] != '-') && (u[x] != '.'))) {
  932.             return 0;
  933.     }
  934.     }
  935.  
  936.     return (x ? 1 : 0);  /* If the first character is ':', it's broken, too */
  937. }
  938.  
  939. int can_exec(const struct stat *finfo) {
  940. #ifdef MULTIPLE_GROUPS
  941.   int cnt;
  942. #endif
  943. #ifdef __EMX__
  944.     /* OS/2 dosen't have Users and Groups */
  945.     return 1;
  946. #else    
  947.     if(user_id == finfo->st_uid)
  948.         if(finfo->st_mode & S_IXUSR)
  949.             return 1;
  950.     if(group_id == finfo->st_gid)
  951.         if(finfo->st_mode & S_IXGRP)
  952.             return 1;
  953. #ifdef MULTIPLE_GROUPS
  954.     for(cnt=0; cnt < NGROUPS_MAX; cnt++) {
  955.         if(group_id_list[cnt] == finfo->st_gid)
  956.             if(finfo->st_mode & S_IXGRP)
  957.                 return 1;
  958.     }
  959. #endif
  960.     return (finfo->st_mode & S_IXOTH);
  961. #endif    
  962. }
  963.  
  964. #ifdef NEED_STRDUP
  965. char *strdup (const char *str)
  966. {
  967.   char *dup;
  968.  
  969.   if(!(dup = (char *)malloc (strlen (str) + 1)))
  970.       return NULL;
  971.   dup = strcpy (dup, str);
  972.  
  973.   return dup;
  974. }
  975. #endif
  976.  
  977. /* The following two routines were donated for SVR4 by Andreas Vogel */
  978. #ifdef NEED_STRCASECMP
  979. int strcasecmp (const char *a, const char *b)
  980. {
  981.     const char *p = a;
  982.     const char *q = b;
  983.     for (p = a, q = b; *p && *q; p++, q++)
  984.     {
  985.       int diff = tolower(*p) - tolower(*q);
  986.       if (diff) return diff;
  987.     }
  988.     if (*p) return 1;       /* p was longer than q */
  989.     if (*q) return -1;      /* p was shorter than q */
  990.     return 0;               /* Exact match */
  991. }
  992.  
  993. #endif
  994.  
  995. #ifdef NEED_STRNCASECMP
  996. int strncasecmp (const char *a, const char *b, int n)
  997. {
  998.     const char *p = a;
  999.     const char *q = b;
  1000.  
  1001.     for (p = a, q = b; /*NOTHING*/; p++, q++)
  1002.     {
  1003.       int diff;
  1004.       if (p == a + n) return 0;     /*   Match up to n characters */
  1005.       if (!(*p && *q)) return *p - *q;
  1006.       diff = tolower(*p) - tolower(*q);
  1007.       if (diff) return diff;
  1008.     }
  1009.     /*NOTREACHED*/
  1010. }
  1011. #endif
  1012.  
  1013.  
  1014.  
  1015. #ifdef NEED_INITGROUPS
  1016. int initgroups(const char *name, gid_t basegid)
  1017. {
  1018. #if defined(QNX) || defined(MPE)
  1019. /* QNX and MPE do not appear to support supplementary groups. */
  1020.     return 0;
  1021. #else /* ndef QNX */
  1022.   gid_t groups[NGROUPS_MAX];
  1023.   struct group *g;
  1024.   int index = 0;
  1025.  
  1026.   setgrent();
  1027.  
  1028.   groups[index++] = basegid;
  1029.  
  1030.   while (index < NGROUPS_MAX && ((g = getgrent()) != NULL))
  1031.     if (g->gr_gid != basegid)
  1032.     {
  1033.       char **names;
  1034.  
  1035.       for (names = g->gr_mem; *names != NULL; ++names)
  1036.         if (!strcmp(*names, name))
  1037.           groups[index++] = g->gr_gid;
  1038.     }
  1039.  
  1040.   endgrent();
  1041.  
  1042.   return setgroups(index, groups);
  1043. #endif /* def QNX */
  1044. }
  1045. #endif /* def NEED_INITGROUPS */
  1046.  
  1047. #ifdef NEED_WAITPID
  1048. /* From ikluft@amdahl.com */
  1049. /* this is not ideal but it works for SVR3 variants */
  1050. /* httpd does not use the options so this doesn't implement them */
  1051. int waitpid(pid_t pid, int *statusp, int options)
  1052. {
  1053.     int tmp_pid;
  1054.     if ( kill ( pid,0 ) == -1) {
  1055.         errno=ECHILD;
  1056.         return -1;
  1057.     }
  1058.     while ((( tmp_pid = wait(statusp)) != pid) && ( tmp_pid != -1 ));
  1059.     return tmp_pid;
  1060. }
  1061. #endif
  1062.  
  1063. int ind(const char *s, char c) {
  1064.     register int x;
  1065.  
  1066.     for(x=0;s[x];x++)
  1067.         if(s[x] == c) return x;
  1068.  
  1069.     return -1;
  1070. }
  1071.  
  1072. int rind(const char *s, char c) {
  1073.     register int x;
  1074.  
  1075.     for(x=strlen(s)-1;x != -1;x--)
  1076.         if(s[x] == c) return x;
  1077.  
  1078.     return -1;
  1079. }
  1080.  
  1081. void str_tolower(char *str) {
  1082.     while(*str) {
  1083.         *str = tolower(*str);
  1084.         ++str;
  1085.     }
  1086. }
  1087.         
  1088. uid_t uname2id(const char *name) {
  1089.     struct passwd *ent;
  1090.  
  1091.     if(name[0] == '#') 
  1092.         return(atoi(&name[1]));
  1093.  
  1094.     if(!(ent = getpwnam(name))) {
  1095.         fprintf(stderr,"httpd: bad user name %s\n",name);
  1096.         exit(1);
  1097.     }
  1098.     return(ent->pw_uid);
  1099. }
  1100.  
  1101. gid_t gname2id(const char *name) {
  1102.     struct group *ent;
  1103.  
  1104.     if(name[0] == '#') 
  1105.         return(atoi(&name[1]));
  1106.  
  1107.     if(!(ent = getgrnam(name))) {
  1108.         fprintf(stderr,"httpd: bad group name %s\n",name);
  1109.         exit(1);
  1110.     }
  1111.     return(ent->gr_gid);
  1112. }
  1113.  
  1114. #if 0
  1115. int get_portnum(int sd) {
  1116.     struct sockaddr addr;
  1117.     int len;
  1118.  
  1119.     len = sizeof(struct sockaddr);
  1120.     if(getsockname(sd,&addr,&len) < 0)
  1121.         return -1;
  1122.     return ntohs(((struct sockaddr_in *)&addr)->sin_port);
  1123. }
  1124.  
  1125. struct in_addr get_local_addr(int sd) {
  1126.     struct sockaddr addr;
  1127.     int len;
  1128.  
  1129.     len = sizeof(struct sockaddr);
  1130.     if(getsockname(sd,&addr,&len) < 0) {
  1131.     perror ("getsockname");
  1132.         fprintf (stderr, "Can't get local host address!\n");
  1133.     exit(1);
  1134.     }
  1135.          
  1136.     return ((struct sockaddr_in *)&addr)->sin_addr;
  1137. }
  1138. #endif
  1139.  
  1140. /*
  1141.  * Parses a host of the form <address>[:port]
  1142.  * :port is permitted if 'port' is not NULL
  1143.  */
  1144. unsigned long get_virthost_addr (const char *w, unsigned short *ports) {
  1145.     struct hostent *hep;
  1146.     unsigned long my_addr;
  1147.     char *p;
  1148.  
  1149.     p = strchr(w, ':');
  1150.     if (ports != NULL)
  1151.     {
  1152.     *ports = 0;
  1153.     if (p != NULL && strcmp(p+1, "*") != 0) *ports = atoi(p+1);
  1154.     }
  1155.  
  1156.     if (p != NULL) *p = '\0';
  1157.     if (strcmp(w, "*") == 0)
  1158.     {
  1159.     if (p != NULL) *p = ':';
  1160.     return htonl(INADDR_ANY);
  1161.     }
  1162.     
  1163. #ifdef DGUX
  1164.     my_addr = inet_network(w);
  1165. #else
  1166.     my_addr = inet_addr(w);
  1167. #endif
  1168.     if (my_addr != INADDR_NONE)
  1169.     {
  1170.     if (p != NULL) *p = ':';
  1171.     return my_addr;
  1172.     }
  1173.  
  1174.     hep = gethostbyname(w);
  1175.         
  1176.     if ((!hep) || (hep->h_addrtype != AF_INET || !hep->h_addr_list[0])) {
  1177.     fprintf (stderr, "Cannot resolve host name %s --- exiting!\n", w);
  1178.     exit(1);
  1179.     }
  1180.         
  1181.     if (hep->h_addr_list[1]) {
  1182.     fprintf(stderr, "Host %s has multiple addresses ---\n", w);
  1183.     fprintf(stderr, "you must choose one explicitly for use as\n");
  1184.     fprintf(stderr, "a virtual host.  Exiting!!!\n");
  1185.     exit(1);
  1186.     }
  1187.         
  1188.     if (p != NULL) *p = ':';
  1189.  
  1190.     return ((struct in_addr *)(hep->h_addr))->s_addr;
  1191. }
  1192.  
  1193.  
  1194. static char *find_fqdn(pool *a, struct hostent *p) {
  1195.     int x;
  1196.  
  1197.     if(ind(p->h_name,'.') == -1) {
  1198.         for(x=0;p->h_aliases[x];++x) {
  1199.             if((ind(p->h_aliases[x],'.') != -1) && 
  1200.                (!strncmp(p->h_aliases[x],p->h_name,strlen(p->h_name))))
  1201.                 return pstrdup(a, p->h_aliases[x]);
  1202.         }
  1203.         return NULL;
  1204.     }
  1205.     return pstrdup(a, (void *)p->h_name);
  1206. }
  1207.  
  1208. char *get_local_host(pool *a)
  1209. {
  1210. #ifndef MAXHOSTNAMELEN
  1211. #define MAXHOSTNAMELEN 256
  1212. #endif
  1213.     char str[MAXHOSTNAMELEN+1];
  1214.     char *server_hostname;
  1215.     struct hostent *p;
  1216.  
  1217.     if( gethostname( str, sizeof( str ) - 1 ) != 0 ) {
  1218.     perror( "Unable to gethostname" );
  1219.     exit(1);
  1220.     }
  1221.     str[MAXHOSTNAMELEN] = '\0';
  1222.     if((!(p=gethostbyname(str))) || (!(server_hostname = find_fqdn(a, p)))) {
  1223.         fprintf(stderr,"httpd: cannot determine local host name.\n");
  1224.     fprintf(stderr,"Use ServerName to set it manually.\n");
  1225.     exit(1);
  1226.     }
  1227.  
  1228.     return server_hostname;
  1229. }
  1230.  
  1231. /* aaaack but it's fast and const should make it shared text page. */
  1232. const int pr2six[256]={
  1233.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  1234.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,
  1235.     52,53,54,55,56,57,58,59,60,61,64,64,64,64,64,64,64,0,1,2,3,4,5,6,7,8,9,
  1236.     10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64,64,26,27,
  1237.     28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
  1238.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  1239.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  1240.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  1241.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  1242.     64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  1243.     64,64,64,64,64,64,64,64,64,64,64,64,64
  1244. };
  1245.  
  1246. char *uudecode(pool *p, const char *bufcoded) {
  1247.     int nbytesdecoded;
  1248.     register unsigned char *bufin;
  1249.     register char *bufplain;
  1250.     register unsigned char *bufout;
  1251.     register int nprbytes;
  1252.     
  1253.     /* Strip leading whitespace. */
  1254.     
  1255.     while(*bufcoded==' ' || *bufcoded == '\t') bufcoded++;
  1256.     
  1257.     /* Figure out how many characters are in the input buffer.
  1258.      * Allocate this many from the per-transaction pool for the result.
  1259.      */
  1260.     bufin = (unsigned char *)bufcoded;
  1261.     while(pr2six[*(bufin++)] <= 63);
  1262.     nprbytes = (char *)bufin - bufcoded - 1;
  1263.     nbytesdecoded = ((nprbytes+3)/4) * 3;
  1264.  
  1265.     bufplain = palloc(p, nbytesdecoded + 1);
  1266.     bufout = (unsigned char *)bufplain;
  1267.     
  1268.     bufin = (unsigned char *)bufcoded;
  1269.     
  1270.     while (nprbytes > 0) {
  1271.         *(bufout++) = 
  1272.             (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
  1273.         *(bufout++) = 
  1274.             (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
  1275.         *(bufout++) = 
  1276.             (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
  1277.         bufin += 4;
  1278.         nprbytes -= 4;
  1279.     }
  1280.     
  1281.     if(nprbytes & 03) {
  1282.         if(pr2six[bufin[-2]] > 63)
  1283.             nbytesdecoded -= 2;
  1284.         else
  1285.             nbytesdecoded -= 1;
  1286.     }
  1287.     bufplain[nbytesdecoded] = '\0';
  1288.     return bufplain;
  1289. }
  1290.  
  1291. #ifdef __EMX__
  1292. void os2pathname(char *path) {
  1293.     char newpath[MAX_STRING_LEN];
  1294.     int loop;
  1295.     int offset;
  1296.  
  1297.     offset = 0;
  1298.     for (loop=0; loop < (strlen(path) + 1) && loop < sizeof(newpath)-1; loop++) {
  1299.         if (path[loop] == '/') {
  1300.             newpath[offset] = '\\';
  1301.             /*
  1302.             offset = offset + 1;
  1303.             newpath[offset] = '\\';
  1304.             */
  1305.         } else
  1306.             newpath[offset] = path[loop];
  1307.         offset = offset + 1;
  1308.     };
  1309.     /* Debugging code */
  1310.     /* fprintf(stderr, "%s \n", newpath); */
  1311.  
  1312.     strcpy(path, newpath);
  1313. };
  1314. #endif
  1315.  
  1316.  
  1317. #ifdef NEED_STRERROR
  1318. char *
  1319. strerror (int err) {
  1320.  
  1321.     char *p;
  1322.     extern char *const sys_errlist[];
  1323.  
  1324.     p = sys_errlist[err];
  1325.     return (p);
  1326. }
  1327. #endif
  1328.  
  1329.  
  1330. int ap_slack (int fd, int line)
  1331. {
  1332. #if !defined(F_DUPFD) || defined(NO_SLACK)
  1333.     return fd;
  1334. #else
  1335.     int new_fd;
  1336.  
  1337. #ifdef HIGH_SLACK_LINE
  1338.     if (line == AP_SLACK_HIGH) {
  1339.     new_fd = fcntl (fd, F_DUPFD, HIGH_SLACK_LINE);
  1340.     if (new_fd != -1) {
  1341.         close (fd);
  1342.         return new_fd;
  1343.     }
  1344.     }
  1345. #endif
  1346.     /* otherwise just assume line == AP_SLACK_LOW */
  1347.     new_fd = fcntl (fd, F_DUPFD, LOW_SLACK_LINE);
  1348.     if (new_fd == -1) {
  1349.       return fd;
  1350.     }
  1351.     close (fd);
  1352.     return new_fd;
  1353. #endif
  1354. }
  1355.