home *** CD-ROM | disk | FTP | other *** search
/ Collection of Internet / Collection of Internet.iso / msdos / lynx / source / www / library / implemen / htparse.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-10-25  |  18.8 KB  |  745 lines

  1. /*        Parse HyperText Document Address        HTParse.c
  2. **        ================================
  3. */
  4.  
  5. #include"capalloc.h"
  6. #include"capstdio.h"
  7. #include "HTUtils.h"
  8. #include "HTParse.h"
  9. #include "tcp.h"
  10.  
  11. #define HEX_ESCAPE '%'
  12.  
  13. struct struct_parts {
  14.     char * access;
  15.     char * host;
  16.     char * absolute;
  17.     char * relative;
  18. /*    char * search;        no - treated as part of path */
  19.     char * anchor;
  20. };
  21.  
  22.  
  23. /*    Strip white space off a string
  24. **    ------------------------------
  25. **
  26. ** On exit,
  27. **    Return value points to first non-white character, or to 0 if none.
  28. **    All trailing white space is OVERWRITTEN with zero.
  29. */
  30.  
  31. #ifdef __STDC__
  32. char * HTStrip(char * s)
  33. #else
  34. char * HTStrip(s)
  35.     char *s;
  36. #endif
  37. {
  38. #define SPACE(c) ((c==' ')||(c=='\t')||(c=='\n')) 
  39.     char * p=s;
  40.     for(p=s;*p;p++);                /* Find end of string */
  41.     for(p--;p>=s;p--) {
  42.         if(SPACE(*p)) *p=0;    /* Zap trailing blanks */
  43.     else break;
  44.     }
  45.     while(SPACE(*s))s++;    /* Strip leading blanks */
  46.     return s;
  47. }
  48.  
  49.  
  50. static void scan(char *cp_name, struct struct_parts *SSPp_parts)    {
  51. /*
  52.  *    Purpose:    Break up an address name into its separate parts.
  53.  *    Arguments:    cp_name        An address (URL) to break up.
  54.  *                    The name may be incomplete.
  55.  *            SSBp_parts    The structure to store the different
  56.  *                    parts in.
  57.  *    Return Value:    void
  58.  *    Remarks/Portability/Dependencies/Restrictions:
  59.  *        The following refers to the members of the passed in
  60.  *        structure upon return of this function:
  61.  *            The absolute xor relative are NULL.
  62.  *            host, anchor, and access may be nonzero if they
  63.  *                were found in the address.
  64.  *            Any nonzero members point to ASCIIZ strings.
  65.  *    Revision History:
  66.  *        ??-??-??    created
  67.  *        03-28-94    modified for DosLynx
  68.  */
  69.     auto char *cp_after_access;
  70.     auto char *cp_p;
  71.     auto signed short int ssi_length = strlen(cp_name);
  72.  
  73.     /*
  74.      *    Initialize all parts of the upcoming parts of the name.
  75.      */
  76.     SSPp_parts->access = SSPp_parts->host = SSPp_parts->absolute =
  77.         SSPp_parts->relative = SSPp_parts->anchor = NULL;
  78.  
  79.     /*
  80.      *    Save a pointer to the start of the original name.
  81.      *    This is a reference to where the access of the address
  82.      *    has ended (i.e. http, ftp, etc...)
  83.      *    There may be no access specified.
  84.      */
  85.     cp_after_access = cp_name;
  86.  
  87.     /*
  88.      *    Loop through the address to set the access name and track
  89.      *    where it ends.
  90.      */
  91.     for(cp_p = cp_name; *cp_p != '\0'; cp_p++)    {
  92.         /*
  93.          *    We loop for a colon which always follows the access
  94.          *    name.
  95.          *    (Except in the case of a specified port number???)
  96.          */
  97.         switch(*cp_p)    {
  98.         /*
  99.          *    Need to break the loop on the following
  100.          *    special characters that make up a URL.
  101.          */
  102.         case '/':
  103.         case '#':
  104.         case '.':
  105.             break;
  106.         case ':':
  107.             /*
  108.              *    End the string here.  No need to keep :
  109.              *    Set the access part of the structure.
  110.              *    Set to where the string continues after the
  111.              *    access string.
  112.              */
  113.             *cp_p = '\0';
  114.             SSPp_parts->access = cp_name;
  115.             cp_after_access = cp_p + 1;
  116.             break;
  117.         default:
  118.             continue;
  119.         }
  120.  
  121.         /*
  122.          *    If code gets here, need to break loop.
  123.          */
  124.         break;
  125.     }
  126.  
  127.     /*
  128.      *    Loop backwards through the address looking for the tag
  129.      *    anchor to first select upon loading.
  130.      */
  131.     for(cp_p = cp_name + ssi_length - 1; cp_p >= cp_name; cp_p--)    {
  132.         /*
  133.          *    Found the tag anchor, terminate the address before
  134.          *    the #, the tag anchor should be the rest of the
  135.          *    address to the end of the address.
  136.          */
  137.         if(*cp_p == '#')    {
  138.             SSPp_parts->anchor = cp_p + 1;
  139.             *cp_p = '\0';
  140.         }
  141.     }
  142.  
  143.     /*
  144.      *    Start back up directly after the specified access type.
  145.      */
  146.     cp_p = cp_after_access;
  147.  
  148.     /*
  149.      *    If there we have a /, a host or root should follow.
  150.      */
  151.     if(*cp_p == '/')    {
  152.         /*
  153.          *    If following the /, we have another /, there is a
  154.          *    host following.
  155.          */
  156.         if(*(cp_p + 1) == '/')    {
  157.             /*
  158.              *    Set the address pointing to the host.
  159.              */
  160.             SSPp_parts->host = cp_p + 2;
  161.  
  162.             /*
  163.              *    Attempt to find the end of the host's name
  164.              *    beginning, of course, with a path /.
  165.              */
  166.             cp_p = strchr(SSPp_parts->host, '/');
  167.  
  168.             /*
  169.              *    A path (root) was found, set the absolute
  170.              *    path with it.
  171.              *    Be sure to terminate the host name.
  172.              */
  173.             if(cp_p != NULL)    {
  174.                 *cp_p = '\0';
  175.                 SSPp_parts->absolute = cp_p + 1;
  176.             }
  177.         }
  178.         else    {
  179.             /*
  180.              *    There was no host specified, must use what
  181.              *    follows as the absolute path (root).
  182.              */
  183.             SSPp_parts->absolute = cp_p + 1;
  184.         }
  185.     }
  186.     else    {
  187.         /*
  188.          *    There is no host or root (path) specification in
  189.          *    the address so it must be relative.
  190.          *    Be careful not to assign an unNULL string if there
  191.          *    is actually nothing inside of it.
  192.          */
  193.         SSPp_parts->relative = (*cp_after_access) ?
  194.             cp_after_access : NULL;
  195.     }
  196.  
  197.     /*
  198.      *    If there was an access type and an anchor specification
  199.      *    but no host, this is an exception.
  200.      *    We must restore the tag anchor symbol # to the address
  201.      *    and set that there is actually no anchor.
  202.      *    In these cases, the anchor is not really an anchor at all.
  203.      *    e.g. news:j462#36487@foo.bar
  204.      */
  205.     if(SSPp_parts->access != NULL && SSPp_parts->host == NULL &&
  206.         SSPp_parts->anchor != NULL)    {
  207.         *(SSPp_parts->anchor - 1) = '#';
  208.         SSPp_parts->anchor = NULL;
  209.     }
  210.  
  211.     /*
  212.      *    All done with the scan.
  213.      */
  214. }
  215.  
  216.  
  217. extern char *HTParse(const char *cp_aName, const char *cp_relatedName,
  218.     signed short int ssi_wanted)    {
  219. /*
  220.  *    Purpose:    Parse an address (URL) name relative to another
  221.  *                (URL) name.
  222.  *    Arguments:    cp_aName    The address to parse.
  223.  *            cp_relatedName    The relative address to parse
  224.  *                    cp_aName with.
  225.  *            ssi_wanted    A mask for the bits which are
  226.  *                    flags on how to parse the address.
  227.  *    Return Value:    char *    A malloced string which is the resulting
  228.  *                address requested according to the flags
  229.  *                which were set and the original and
  230.  *                and relative name.
  231.  *    Remarks/Portability/Dependencies/Restrictions:
  232.  *        All calling functions should free the memory allocated by
  233.  *        HTParse once finished with the return value.
  234.  *    Revision History:
  235.  *        ??-??-??    created
  236.  *        03-28-94    modified for DosLynx
  237.  */
  238.  
  239.     auto char *cp_result;
  240.     auto char *cp_return_value = NULL;
  241.     auto signed short int ssi_len;
  242.     auto char *cp_name = NULL;
  243.     auto char *cp_rel = NULL;
  244.     auto char *cp_p;
  245.     auto char *cp_access;
  246.     auto struct struct_parts SSP_given, SSP_related;
  247.  
  248.     /*
  249.      *    Copy the input strings so that we can split them up into
  250.      *    their parts.
  251.      */
  252.     ssi_len = strlen(cp_aName) + strlen(cp_relatedName) + 10;
  253.     /*
  254.      *    Allocate space; more than enough.
  255.      */
  256.     cp_result = (char *)malloc(ssi_len);
  257.     /*
  258.      *    Report error on not enough memory to allocate.
  259.      */
  260.     if(cp_result == NULL)    {
  261.         outofmem(__FILE__, "HTParse");
  262.     }
  263.  
  264.     /*
  265.      *    Copy over the two names, allocating memory while doing so.
  266.      *    Question:  How does StrAllocCopy change the pointer value?
  267.      *    Answer:  Must be a macro, or this is a bug.
  268.      *    It's a macro in HTString, calls HTSACopy
  269.      */
  270.     StrAllocCopy(cp_name, cp_aName);
  271.     StrAllocCopy(cp_rel, cp_relatedName);
  272.  
  273.     /*
  274.      *    Break the allocated names up into their respective parts.
  275.      */
  276.     scan(cp_name, &SSP_given);
  277.     scan(cp_rel,  &SSP_related);
  278.  
  279.     /*
  280.      *    Begin building the requested address.
  281.      */
  282.     *cp_result = '\0';
  283.  
  284.     cp_access = SSP_given.access ? SSP_given.access :
  285.         SSP_related.access;
  286.  
  287.     /*
  288.      *    Requesting the access, if there is any, be sent back.
  289.      */
  290.     if(ssi_wanted & PARSE_ACCESS)    {
  291.         if(cp_access)    {
  292.             strcat(cp_result, cp_access);
  293.             /*
  294.              *    Requestor also wants full URL style
  295.              *    return.  Put in a : to separate access.
  296.              */
  297.             if(ssi_wanted & PARSE_PUNCTUATION)    {
  298.                 strcat(cp_result, ":");
  299.             }
  300.         }
  301.     }
  302.  
  303.     /*
  304.      *    If the access is not specified on either the given address
  305.      *    or related address and then also if they are not the same,
  306.      *    then disregard all related information.
  307.      */
  308.     if (SSP_given.access && SSP_related.access)    {
  309.         if(strcmp(SSP_given.access, SSP_related.access) != 0)    {
  310.             SSP_related.host =
  311.             SSP_related.absolute =
  312.             SSP_related.relative =
  313.             SSP_related.anchor = NULL;
  314.         }
  315.     }
  316.  
  317.     /*
  318.      *    If requesting the host in the return.
  319.      */
  320.     if(ssi_wanted & PARSE_HOST)    {
  321.         /*
  322.          *    If either the given or related address has a host
  323.          */
  324.         if(SSP_given.host != NULL || SSP_related.host != NULL)    {
  325.             /*
  326.              *    Figure where to add the host.
  327.              */
  328.             auto char *cp_tail = cp_result + strlen(cp_result);
  329.             /*
  330.              *    If exact URL punctuation requested, add the
  331.              *    leading //
  332.              */
  333.             if(ssi_wanted & PARSE_PUNCTUATION)    {
  334.                 strcat(cp_result, "//");
  335.             }
  336.             /*
  337.              *    Append the host
  338.              */
  339.             strcat(cp_result, SSP_given.host != NULL ?
  340.                 SSP_given.host : SSP_related.host);
  341.  
  342.             /*
  343.              *    We must ignore default port numbers and
  344.              *    trailing dots on FQDNs(?) which will cause
  345.              *    identical addresses to look different.
  346.              */
  347.             {
  348.                 /*
  349.                  *    Find a : in the host.
  350.                  */
  351.                 auto char *cp = strchr(cp_tail, ':');
  352.                 /*
  353.                  *    If a port was specified.
  354.                  */
  355.                 if(cp != NULL && cp_access != NULL)    {
  356.                     /*
  357.                      *    Check for redundant access
  358.                      *    types and port numbers.
  359.                      */
  360.                     if((strcmp(cp_access, "http") == 0
  361.                         && strcmp(cp, ":80") == 0)
  362.                         || (strcmp(cp_access,
  363.                         "gopher") == 0 && strcmp(cp,
  364.                         ":70") == 0))    {
  365.                         /*
  366.                          *    Redundant, end the
  367.                          *    return address
  368.                          *    before the port is
  369.                          *    specified.
  370.                          */
  371.                         *cp = '\0';
  372.                     }
  373.                 }
  374.                 /*
  375.                  *    No redundant port specified.
  376.                  */
  377.                 else if(cp == NULL)    {
  378.                     /*
  379.                      *    Set to end of hostname.
  380.                      */
  381.                     cp = cp_tail + strlen(cp_tail);
  382.                 }
  383.  
  384.                 /*
  385.                  *    Back up one since beyond actual
  386.                  *    end of the hostname.
  387.                  */
  388.                 cp--;
  389.  
  390.                 /*
  391.                  *    If there is a period at the end of
  392.                  *    the hostname, kill it.
  393.                  */
  394.                 if(*cp == '.')    {
  395.                     *cp = '\0';
  396.                 }
  397.             }
  398.         }
  399.     }
  400.  
  401.     /*
  402.      *    If there are different hosts, no relative path will be
  403.      *    assumed.
  404.      */
  405.     if(SSP_given.host != NULL && SSP_related.host != NULL)    {
  406.         if(strcmp(SSP_given.host, SSP_related.host) != 0)    {
  407.             SSP_related.absolute =
  408.             SSP_related.relative =
  409.             SSP_related.anchor = NULL;
  410.         }
  411.     }
  412.  
  413.     /*
  414.      *    If the path is also part of the requested return.
  415.      */
  416.     if(ssi_wanted & PARSE_PATH)    {
  417.         /*
  418.          *    If the absolute (full) path is already given.
  419.          */
  420.         if(SSP_given.absolute != NULL)    {
  421.             /*
  422.              *    Requesting the full URL punctuation
  423.              */
  424.             if(ssi_wanted & PARSE_PUNCTUATION)    {
  425.                 strcat(cp_result, "/");
  426.             }
  427.             /*
  428.              *    Append the absolute path.
  429.              */
  430.             strcat(cp_result, SSP_given.absolute);
  431.         }
  432.         /*
  433.          *    Otherwise, we must adopt the given path but not
  434.          *    the file name.
  435.          */
  436.         else if(SSP_related.absolute != NULL)    {
  437.             /*
  438.              *    Append the leading /
  439.              *    Shouldn't we check for PARSE_PUNCTUATION?
  440.              *    Doing so, possible error.
  441.              *    Append the relative absolute path.
  442.              */
  443.             if(ssi_wanted & PARSE_PUNCTUATION)    {
  444.                 strcat(cp_result, "/");
  445.             }
  446.             strcat(cp_result, SSP_related.absolute);
  447.  
  448.             /*
  449.              *    Check to see if we have a relative path
  450.              *    to further evaluate and append.
  451.              */
  452.             if(SSP_given.relative != NULL)    {
  453.                 /*
  454.                  *    See if there is a search directive
  455.                  *    in the address, if so avoid it.
  456.                  *    If there isn't set to the end of
  457.                  *    the address.
  458.                  */
  459.                 cp_p = strchr(cp_result, '?');
  460.                 if(cp_p == NULL)    {
  461.                     cp_p = cp_result + strlen(cp_result)
  462.                         - 1;
  463.                 }
  464.  
  465.                 /*
  466.                  *    Find the last / by backing up and
  467.                  *    finding it.
  468.                  */
  469.                 for(; *cp_p != '/'; cp_p--)
  470.                     /* NULL body */;
  471.  
  472.                 /*
  473.                  *    Remove the file name from the
  474.                  *    address and add the given relative
  475.                  *    path and file.
  476.                  */
  477.                 *(cp_p + 1) = '\0';
  478.                 strcat(cp_result, SSP_given.relative);
  479.  
  480.                 /*
  481.                  *    Simplyfy the resulting address by
  482.                                  *    taking out .. and . stuff
  483.                  */
  484.                 HTSimplify(cp_result);
  485.             }
  486.         }
  487.         /*
  488.          *    Otherwise we use what we have got.
  489.          */
  490.         else if(SSP_given.relative != NULL)    {
  491.             strcat(cp_result, SSP_given.relative);
  492.         }
  493.         else if(SSP_related.relative != NULL)    {
  494.             strcat(cp_result, SSP_related.relative);
  495.         }
  496.         else {
  497.             /*
  498.              *    No inheritance at all.
  499.              */
  500.             strcat(cp_result, "/");
  501.         }
  502.     }
  503.  
  504.     /*
  505.      *    If the anchor is requested also.
  506.      */
  507.     if(ssi_wanted & PARSE_ANCHOR)    {
  508.         if(SSP_given.anchor != NULL || SSP_related.anchor != NULL)
  509.         {
  510.             /*
  511.              *    Keep punctuation if requested.
  512.              */
  513.             if(ssi_wanted & PARSE_PUNCTUATION)    {
  514.                 strcat(cp_result, "#");
  515.             }
  516.             strcat(cp_result, SSP_given.anchor != NULL ?
  517.                 SSP_given.anchor : SSP_related.anchor);
  518.         }
  519.     }
  520.  
  521.     /*
  522.      *    Free up the copied anchors.
  523.      *    This also frees the memory pointed to by our SSP_* structs
  524.      */
  525.     free(cp_rel);
  526.     free(cp_name);
  527.  
  528.     /*
  529.      *    Allocate a new string that will be the correct length.
  530.      */
  531.     StrAllocCopy(cp_return_value, cp_result);
  532.     free(cp_result);
  533.     return(cp_return_value);
  534. }
  535.  
  536.  
  537. /*            Simplify a filename
  538. //        -------------------
  539. //
  540. // A unix-style file is allowed to contain the seqeunce xxx/../ which may be
  541. // replaced by "" , and the seqeunce "/./" which may be replaced by "/".
  542. // Simplification helps us recognize duplicate filenames.
  543. //
  544. //    Thus,     /etc/junk/../fred     becomes    /etc/fred
  545. //        /etc/junk/./fred    becomes    /etc/junk/fred
  546. //
  547. //      but we should NOT change
  548. //        http://fred.xxx.edu/../..
  549. //
  550. //    or    ../../albert.html
  551. */
  552. #ifdef __STDC__
  553. void HTSimplify(char * filename)
  554. #else
  555. void HTSimplify(filename)
  556.     char * filename;
  557. #endif
  558.  
  559. {
  560.     char * p;
  561.     char * q;
  562.     if (filename[0] && filename[1])    /* Bug fix 12 Mar 93 TBL */
  563.      for(p=filename+2; *p; p++) {
  564.         if (*p=='/') {
  565.         if ((p[1]=='.') && (p[2]=='.') && (p[3]=='/' || !p[3] )) {
  566.         for (q=p-1; (q>=filename) && (*q!='/'); q--); /* prev slash */
  567.         if (q[0]=='/' && 0!=strncmp(q, "/../", 4)
  568.             &&!(q-1>filename && q[-1]=='/')) {
  569.                 strcpy(q, p+3);    /* Remove  /xxx/..    */
  570.             if (!*filename) strcpy(filename, "/");
  571.             p = q-1;        /* Start again with prev slash     */
  572.         } else {            /*   xxx/.. leave it!    */
  573. #ifdef BUG_CODE
  574.             strcpy(filename, p[3] ? p+4 : p+3); /* rm  xxx/../    */
  575.             p = filename;        /* Start again */
  576. #endif
  577.         }
  578.         } else if ((p[1]=='.') && (p[2]=='/' || !p[2])) {
  579.             strcpy(p, p+2);            /* Remove a slash and a dot */
  580.         }
  581.     }
  582.     }
  583. }
  584.  
  585.  
  586. /*        Make Relative Name
  587. **        ------------------
  588. **
  589. ** This function creates and returns a string which gives an expression of
  590. ** one address as related to another. Where there is no relation, an absolute
  591. ** address is retured.
  592. **
  593. **  On entry,
  594. **    Both names must be absolute, fully qualified names of nodes
  595. **    (no anchor bits)
  596. **
  597. **  On exit,
  598. **    The return result points to a newly allocated name which, if
  599. **    parsed by HTParse relative to relatedName, will yield aName.
  600. **    The caller is responsible for freeing the resulting name later.
  601. **
  602. */
  603. #ifdef __STDC__
  604. char * HTRelative(const char * aName, const char *relatedName)
  605. #else
  606. char * HTRelative(aName, relatedName)
  607.    char * aName;
  608.    char * relatedName;
  609. #endif
  610. {
  611.     char * result = 0;
  612.     CONST char *p = aName;
  613.     CONST char *q = relatedName;
  614.     CONST char * after_access = 0;
  615.     CONST char * path = 0;
  616.     CONST char * last_slash = 0;
  617.     int slashes = 0;
  618.     
  619.     for(;*p; p++, q++) {    /* Find extent of match */
  620.         if (*p!=*q) break;
  621.     if (*p==':') after_access = p+1;
  622.     if (*p=='/') {
  623.         last_slash = p;
  624.         slashes++;
  625.         if (slashes==3) path=p;
  626.     }
  627.     }
  628.     
  629.     /* q, p point to the first non-matching character or zero */
  630.     
  631.     if (!after_access) {            /* Different access */
  632.         StrAllocCopy(result, aName);
  633.     } else if (slashes<3){            /* Different nodes */
  634.         StrAllocCopy(result, after_access);
  635.     } else if (slashes==3){            /* Same node, different path */
  636.         StrAllocCopy(result, path);
  637.     } else {                    /* Some path in common */
  638.         int levels= 0;
  639.         for(; *q && (*q!='#'); q++)  if (*q=='/') levels++;
  640.     result = (char *)malloc(3*levels + strlen(last_slash) + 1);
  641.       if (result == NULL) outofmem(__FILE__, "HTRelative");
  642.     result[0]=0;
  643.     for(;levels; levels--)strcat(result, "../");
  644.     strcat(result, last_slash+1);
  645.     }
  646. #ifndef RELEASE
  647.     if (TRACE) fprintf(stderr, "HT: `%s' expressed relative to\n    `%s' is\n   `%s'.",
  648.         aName, relatedName, result);
  649. #endif /* RELEASE */
  650.     return result;
  651. }
  652.  
  653.  
  654. /*        Escape undesirable characters using %        HTEscape()
  655. **        -------------------------------------
  656. **
  657. **    This function takes a pointer to a string in which
  658. **    some characters may be unacceptable unescaped.
  659. **    It returns a string which has these characters
  660. **    represented by a '%' character followed by two hex digits.
  661. **
  662. **    Unlike HTUnEscape(), this routine returns a malloced string.
  663. */
  664.  
  665. PRIVATE CONST unsigned char isAcceptable[96] =
  666.  
  667. /*    Bit 0        xalpha        -- see HTFile.h
  668. **    Bit 1        xpalpha        -- as xalpha but with plus.
  669. **    Bit 3 ...    path        -- as xpalphas but with /
  670. */
  671.     /*   0 1 2 3 4 5 6 7 8 9 A B C D E F */
  672.     {    0,0,0,0,0,0,0,0,0,0,7,6,0,7,7,4,    /* 2x   !"#$%&'()*+,-./     */
  673.          7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,    /* 3x  0123456789:;<=>?     */
  674.      7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,    /* 4x  @ABCDEFGHIJKLMNO  */
  675.      7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7,    /* 5X  PQRSTUVWXYZ[\]^_     */
  676.      0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,    /* 6x  `abcdefghijklmno     */
  677.      7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0 };    /* 7X  pqrstuvwxyz{\}~    DEL */
  678.  
  679. PRIVATE char *hex = "0123456789ABCDEF";
  680.  
  681. PUBLIC char * HTEscape ARGS2 (CONST char *, str,
  682.     unsigned char, mask)
  683. {
  684. #define ACCEPTABLE(a)    ( a>=32 && a<128 && ((isAcceptable[a-32]) & mask))
  685.     CONST char * p;
  686.     char * q;
  687.     char * result;
  688.     int unacceptable = 0;
  689.     for(p=str; *p; p++)
  690.         if (!ACCEPTABLE((unsigned char)TOASCII(*p)))
  691.         unacceptable++;
  692.     result = (char *) malloc(p-str + unacceptable+ unacceptable + 1);
  693.     if (result == NULL) outofmem(__FILE__, "HTEscape");
  694.     for(q=result, p=str; *p; p++) {
  695.         unsigned char a = TOASCII(*p);
  696.     if (!ACCEPTABLE(a)) {
  697.         *q++ = HEX_ESCAPE;    /* Means hex commming */
  698.         *q++ = hex[a >> 4];
  699.         *q++ = hex[a & 15];
  700.     }
  701.     else *q++ = *p;
  702.     }
  703.     *q++ = 0;            /* Terminate */
  704.     return result;
  705. }
  706.  
  707.  
  708. /*        Decode %xx escaped characters            HTUnEscape()
  709. **        -----------------------------
  710. **
  711. **    This function takes a pointer to a string in which some
  712. **    characters may have been encoded in %xy form, where xy is
  713. **    the acsii hex code for character 16x+y.
  714. **    The string is converted in place, as it will never grow.
  715. */
  716.  
  717. PRIVATE char from_hex ARGS1(char, c)
  718. {
  719.     return  c >= '0' && c <= '9' ?  c - '0' 
  720.             : c >= 'A' && c <= 'F'? c - 'A' + 10
  721.             : c - 'a' + 10;    /* accept small letters just in case */
  722. }
  723.  
  724. PUBLIC char * HTUnEscape ARGS1( char *, str)
  725. {
  726.     char * p = str;
  727.     char * q = str;
  728.     while(*p) {
  729.         if (*p == HEX_ESCAPE) {
  730.         p++;
  731.         if (*p) *q = from_hex(*p++) * 16;
  732.         if (*p) *q = FROMASCII(*q + from_hex(*p++));
  733.         q++;
  734.     } else {
  735.         *q++ = *p++; 
  736.     }
  737.     }
  738.     
  739.     *q++ = 0;
  740.     return str;
  741.     
  742. } /* HTUnEscape */
  743.  
  744.  
  745.