home *** CD-ROM | disk | FTP | other *** search
/ Dream 52 / Amiga_Dream_52.iso / Linux / Divers / lynx2.8.1dev.10.tar.gz / lynx2.8.1dev.10.tar / lynx2-8 / src / LYCookie.c < prev    next >
C/C++ Source or Header  |  1998-05-02  |  63KB  |  2,271 lines

  1. /*                   Lynx Cookie Support           LYCookie.c
  2. **                   ===================
  3. **
  4. **    Author: AMK    A.M. Kuchling (amk@magnet.com)    12/25/96
  5. **
  6. **    Incorporated with mods by FM            01/16/97
  7. **
  8. **  Based on:
  9. **    http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-mgmt-05.txt
  10. **
  11. **    Updated for:
  12. **   http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
  13. **        - FM                    1997-07-09
  14. **
  15. **    Updated for:
  16. **   ftp://ds.internic.net/internet-drafts/draft-ietf-http-state-man-mec-03.txt
  17. **        - FM                    1997-08-02
  18. **
  19. **  TO DO: (roughly in order of decreasing priority)
  20.       * A means to specify "always allow" and "never allow" domains via
  21.     a configuration file is needed.
  22.       * Hex escaping isn't considered at all.  Any semi-colons, commas,
  23.     or spaces actually in cookie names or values (i.e., not serving
  24.     as punctuation for the overall Set-Cookie value) should be hex
  25.     escaped if not quoted, but presumeably the server is expecting
  26.     them to be hex escaped in our Cookie request header as well, so
  27.     in theory we need not unescape them.  We'll see how this works
  28.     out in practice.
  29.       * The prompt should show more information about the cookie being
  30.     set in Novice mode.
  31.       * The truncation heuristic in HTConfirmCookie should probably be
  32.     smarter, smart enough to leave a really short name/value string
  33.     alone.
  34.       * We protect against denial-of-service attacks (see section 6.3.1
  35.     of the draft) by limiting a domain to 50 cookies, limiting the
  36.     total number of cookies to 500, and limiting a processed cookie
  37.     to a maximum of 4096 bytes, but we count on the normal garbage
  38.     collections to bring us back down under the limits, rather than
  39.     actively removing cookies and/or domains based on age or frequency
  40.     of use.
  41.       * If a cookie has the secure flag set, we presently treat only SSL
  42.     connections as secure.    This may need to be expanded for other
  43.     secure communication protocols that become standarized.
  44.       * Cookies could be optionally stored in a file from session to session.
  45. */
  46.  
  47. #include <HTUtils.h>
  48. #include <tcp.h>
  49. #include <HTAccess.h>
  50. #include <HTParse.h>
  51. #include <HTAlert.h>
  52. #include <LYCurses.h>
  53. #include <LYSignal.h>
  54. #include <LYUtils.h>
  55. #include <LYCharUtils.h>
  56. #include <LYClean.h>
  57. #include <LYGlobalDefs.h>
  58. #include <LYEdit.h>
  59. #include <LYStrings.h>
  60. #include <LYSystem.h>
  61. #include <GridText.h>
  62. #include <LYUtils.h>
  63. #include <LYCharUtils.h>
  64. #include <LYCookie.h>
  65.  
  66. #include <LYLeaks.h>
  67.  
  68. #define FREE(x) if (x) {free(x); x = NULL;}
  69.  
  70. /*
  71. **  The first level of the cookie list is a list indexed by the domain
  72. **  string; cookies with the same domain will be placed in the same
  73. **  list.  Thus, finding the cookies that apply to a given URL is a
  74. **  two-level scan; first we check each domain to see if it applies,
  75. **  and if so, then we check the paths of all the cookies on that
  76. **  list.   We keep a running total of cookies as we add or delete
  77. **  them
  78. */
  79. PRIVATE HTList *domain_list = NULL;
  80. PRIVATE HTList *cookie_list = NULL;
  81. PRIVATE int total_cookies = 0;
  82.  
  83. struct _cookie {
  84.     char *lynxID;  /* Lynx cookie identifier */
  85.     char *name;    /* Name of this cookie */
  86.     char *value;   /* Value of this cookie */
  87.     int version;   /* Cookie protocol version (=1) */
  88.     char *comment; /* Comment to show to user */
  89.     char *commentURL; /* URL for comment to show to user */
  90.     char *domain;  /* Domain for which this cookie is valid */
  91.     int port;       /* Server port from which this cookie was given (usu. 80) */
  92.     char *PortList;/* List of ports for which cookie can be sent */
  93.     char *path;    /* Path prefix for which this cookie is valid */
  94.     int pathlen;   /* Length of the path */
  95.     int flags;       /* Various flags */
  96.     time_t expires;/* The time when this cookie expires */
  97.     BOOL quoted;   /* Was a value quoted in the Set-Cookie header? */
  98. };
  99. typedef struct _cookie cookie;
  100.  
  101. #define COOKIE_FLAG_SECURE 1       /* If set, cookie requires secure links */
  102. #define COOKIE_FLAG_DISCARD 2       /* If set, expire at end of session */
  103. #define COOKIE_FLAG_EXPIRES_SET 4  /* If set, an expiry date was set */
  104. #define COOKIE_FLAG_DOMAIN_SET 8   /* If set, an non-default domain was set */
  105. #define COOKIE_FLAG_PATH_SET 16    /* If set, an non-default path was set */
  106. struct _HTStream
  107. {
  108.   HTStreamClass * isa;
  109. };
  110.  
  111. PRIVATE void MemAllocCopy ARGS3(
  112.     char **,    dest,
  113.     CONST char *,    start,
  114.     CONST char *,    end)
  115. {
  116.     char *temp;
  117.  
  118.     if (!(start && end) || (end <= start)) {
  119.     HTSACopy(dest, "");
  120.     return;
  121.     }
  122.  
  123.     temp = (char *)calloc(1, ((end - start) + 1));
  124.     if (temp == NULL)
  125.     outofmem(__FILE__, "MemAllocCopy");
  126.     LYstrncpy(temp, start, (end - start));
  127.     HTSACopy(dest, temp);
  128.     FREE(temp);
  129. }
  130.  
  131. PRIVATE cookie * newCookie NOARGS
  132. {
  133.     cookie *p = (cookie *)calloc(1, sizeof(cookie));
  134.     char lynxID[64];
  135.  
  136.     if (p == NULL)
  137.     outofmem(__FILE__, "newCookie");
  138.     sprintf(lynxID, "%p", p);
  139.     StrAllocCopy(p->lynxID, lynxID);
  140.     p->port = 80;
  141.     return p;
  142. }
  143.  
  144. PRIVATE void freeCookie ARGS1(
  145.     cookie *,    co)
  146. {
  147.     if (co) {
  148.     FREE(co->lynxID);
  149.     FREE(co->name);
  150.     FREE(co->value);
  151.     FREE(co->comment);
  152.     FREE(co->commentURL);
  153.     FREE(co->domain);
  154.     FREE(co->path);
  155.     FREE(co->PortList);
  156.     FREE(co);
  157.     }
  158. }
  159.  
  160. PRIVATE void LYCookieJar_free NOARGS
  161. {
  162.     HTList *dl = domain_list;
  163.     domain_entry *de = NULL;
  164.     HTList *cl = NULL, *next = NULL;
  165.     cookie *co = NULL;
  166.  
  167.     while (dl) {
  168.     if ((de = dl->object) != NULL) {
  169.         cl = de->cookie_list;
  170.         while (cl) {
  171.         next = cl->next;
  172.         co = cl->object;
  173.         if (co) {
  174.             HTList_removeObject(de->cookie_list, co);
  175.             freeCookie(co);
  176.         }
  177.         cl = next;
  178.         }
  179.         FREE(de->domain);
  180.         HTList_delete(de->cookie_list);
  181.         de->cookie_list = NULL;
  182.     }
  183.     dl = dl->next;
  184.     }
  185.     if (dump_output_immediately) {
  186.     cl = cookie_list;
  187.     while (cl) {
  188.         next = cl->next;
  189.         co = cl->object;
  190.         if (co) {
  191.         HTList_removeObject(cookie_list, co);
  192.         freeCookie(co);
  193.         }
  194.         cl = next;
  195.     }
  196.     HTList_delete(cookie_list);
  197.     }
  198.     cookie_list = NULL;
  199.     HTList_delete(domain_list);
  200.     domain_list = NULL;
  201. }
  202.  
  203. /*
  204. **  Compare two hostnames as specified in Section 2 of:
  205. **   http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
  206. **    - AK & FM
  207. */
  208. PRIVATE BOOLEAN host_matches ARGS2(
  209.     CONST char *,    A,
  210.     CONST char *,    B)
  211. {
  212.     /*
  213.      *    The following line will handle both numeric IP addresses and
  214.      *    FQDNs.    Do numeric addresses require special handling?
  215.      */
  216.     if (*B != '.' && !strcmp(A, B))
  217.     return YES;
  218.  
  219.     /*
  220.      *    The following will pass a "dotted tail" match to "a.b.c.e"
  221.      *    as described in Section 2 of the -05 draft.
  222.      */
  223.     if (*B == '.') {
  224.     int diff = (strlen(A) - strlen(B));
  225.     if (diff > 0) {
  226.         if (!strcmp((A + diff), B))
  227.         return YES;
  228.     }
  229.     }
  230.     return NO;
  231. }
  232.  
  233. /*
  234. **  Compare the current port with a port list as specified in Section 4.3 of:
  235. **   http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
  236. **    - FM
  237. */
  238. PRIVATE BOOLEAN port_matches ARGS2(
  239.     int,        port,
  240.     CONST char *,    list)
  241. {
  242.     CONST char *number = list;
  243.  
  244.     if (!(number && isdigit(*number)))
  245.     return(FALSE);
  246.  
  247.     while (*number != '\0') {
  248.     if (atoi(number) == port) {
  249.         return(TRUE);
  250.     }
  251.     while (isdigit(*number)) {
  252.         number++;
  253.     }
  254.     while (*number != '\0' && !isdigit(*number)) {
  255.         number++;
  256.     }
  257.     }
  258.  
  259.     return(FALSE);
  260. }
  261.  
  262. /*
  263. **  Store a cookie somewhere in the domain list. - AK & FM
  264. */
  265. PRIVATE void store_cookie ARGS3(
  266.     cookie *,    co,
  267.     CONST char *,    hostname,
  268.     CONST char *,    path)
  269. {
  270.     HTList *hl, *next;
  271.     cookie *c2;
  272.     time_t now = time(NULL);
  273.     int pos;
  274.     CONST char *ptr;
  275.     domain_entry *de = NULL;
  276.     BOOL Replacement = FALSE;
  277.  
  278.     if (co == NULL)
  279.     return;
  280.  
  281.     /*
  282.      *    Apply sanity checks.
  283.      *
  284.      *    Section 4.3.2, condition 1: The value for the Path attribute is
  285.      *    not a prefix of the request-URI.
  286.      */
  287.     if (strncmp(co->path, path, co->pathlen) != 0) {
  288.     CTRACE(tfp, "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
  289.             co->path, path);
  290.     freeCookie(co);
  291.     co = NULL;
  292.     return;
  293.     }
  294.     /*
  295.      *    The next 4 conditions do NOT apply if the domain is still
  296.      *    the default of request-host.
  297.      */
  298.     if (strcmp(co->domain, hostname) != 0) {
  299.     /*
  300.      *  The hostname does not contain a dot.
  301.      */
  302.     if (strchr(hostname, '.') == NULL) {
  303.         CTRACE(tfp, "store_cookie: Rejecting because '%s' has no dot.\n",
  304.             hostname);
  305.         freeCookie(co);
  306.         co = NULL;
  307.         return;
  308.     }
  309.  
  310.     /*
  311.      *  Section 4.3.2, condition 2: The value for the Domain attribute
  312.      *  contains no embedded dots or does not start with a dot.
  313.      *  (A dot is embedded if it's neither the first nor last character.)
  314.      *  Note that we added a lead dot ourselves if a domain attribute
  315.      *  value otherwise qualified. - FM
  316.      */
  317.     if (co->domain[0] != '.' || co->domain[1] == '\0') {
  318.         CTRACE(tfp, "store_cookie: Rejecting domain '%s'.\n",
  319.             co->domain);
  320.         freeCookie(co);
  321.         co = NULL;
  322.         return;
  323.     }
  324.     ptr = strchr((co->domain + 1), '.');
  325.     if (ptr == NULL || ptr[1] == '\0') {
  326.         CTRACE(tfp, "store_cookie: Rejecting domain '%s'.\n",
  327.             co->domain);
  328.         freeCookie(co);
  329.         co = NULL;
  330.         return;
  331.     }
  332.  
  333.     /*
  334.      *  Section 4.3.2, condition 3: The value for the request-host does
  335.      *  not domain-match the Domain attribute.
  336.      */
  337.     if (!host_matches(hostname, co->domain)) {
  338.         CTRACE(tfp, "store_cookie: Rejecting domain '%s' for host '%s'.\n",
  339.             co->domain, hostname);
  340.         freeCookie(co);
  341.         co = NULL;
  342.         return;
  343.     }
  344.  
  345.     /*
  346.      *  Section 4.3.2, condition 4: The request-host is an HDN (not IP
  347.      *  address) and has the form HD, where D is the value of the Domain
  348.      *  attribute, and H is a string that contains one or more dots.
  349.      */
  350.     ptr = ((hostname + strlen(hostname)) - strlen(co->domain));
  351.     if (strchr(hostname, '.') < ptr) {
  352.         char *msg = calloc(1,
  353.                    (strlen(co->domain) +
  354.                 strlen(hostname) +
  355.                 strlen(INVALID_COOKIE_DOMAIN_CONFIRMATION) +
  356.                 1));
  357.  
  358.         sprintf(msg,
  359.             INVALID_COOKIE_DOMAIN_CONFIRMATION,
  360.             co->domain,
  361.             hostname);
  362.         if (!HTConfirm(msg)) {
  363.         CTRACE(tfp, "store_cookie: Rejecting domain '%s' for host '%s'.\n",
  364.                 co->domain,
  365.                 hostname);
  366.         freeCookie(co);
  367.         co = NULL;
  368.         FREE(msg);
  369.         return;
  370.         }
  371.         FREE(msg);
  372.     }
  373.     }
  374.  
  375.     /*
  376.      *    Ensure that the domain list exists.
  377.      */
  378.     if (domain_list == NULL) {
  379.     atexit(LYCookieJar_free);
  380.     domain_list = HTList_new();
  381.     total_cookies = 0;
  382.     }
  383.  
  384.     /*
  385.      *    Look through domain_list to see if the cookie's domain
  386.      *    is already listed.
  387.      */
  388.     if (dump_output_immediately) { /* Non-interactive, can't respond */
  389.     if (cookie_list == NULL)
  390.         cookie_list = HTList_new();
  391.     } else {
  392.     cookie_list = NULL;
  393.     for (hl = domain_list; hl != NULL; hl = hl->next) {
  394.         de = (domain_entry *)hl->object;
  395.         if ((de != NULL && de->domain != NULL) &&
  396.         !strcmp(co->domain, de->domain)) {
  397.         cookie_list = de->cookie_list;
  398.         break;
  399.         }
  400.     }
  401.     if (hl == NULL) {
  402.         /*
  403.          *    Domain not found; add a new entry for this domain.
  404.          */
  405.         de = (domain_entry *)calloc(1, sizeof(domain_entry));
  406.         if (de == NULL)
  407.         outofmem(__FILE__, "store_cookie");
  408.         de->bv = QUERY_USER;
  409.         cookie_list = de->cookie_list = HTList_new();
  410.         StrAllocCopy(de->domain, co->domain);
  411.         HTList_addObject(domain_list, de);
  412.     }
  413.     }
  414.  
  415.     /*
  416.      *    Loop over the cookie list, deleting expired and matching cookies.
  417.      */
  418.     hl = cookie_list;
  419.     pos = 0;
  420.     while (hl) {
  421.     c2 = (cookie *)hl->object;
  422.     next = hl->next;
  423.     /*
  424.      *  Check if this cookie has expired.
  425.      */
  426.     if ((c2 != NULL) &&
  427.         (c2->flags & COOKIE_FLAG_EXPIRES_SET) &&
  428.         c2->expires < now) {
  429.         HTList_removeObject(cookie_list, c2);
  430.         freeCookie(c2);
  431.         c2 = NULL;
  432.         total_cookies--;
  433.  
  434.     /*
  435.      *  Check if this cookie matches the one we're inserting.
  436.      */
  437.     } else if ((c2) &&
  438.            !strcmp(co->domain, c2->domain) &&
  439.            !strcmp(co->path, c2->path) &&
  440.            !strcmp(co->name, c2->name)) {
  441.         HTList_removeObject(cookie_list, c2);
  442.         freeCookie(c2);
  443.         c2 = NULL;
  444.         total_cookies--;
  445.         Replacement = TRUE;
  446.  
  447.     } else if ((c2) && (c2->pathlen) > (co->pathlen)) {
  448.         pos++;
  449.     }
  450.     hl = next;
  451.     }
  452.  
  453.     /*
  454.      *    Don't bother to add the cookie if it's already expired.
  455.      */
  456.     if ((co->flags & COOKIE_FLAG_EXPIRES_SET) && co->expires < now) {
  457.     freeCookie(co);
  458.     co = NULL;
  459.  
  460.     /*
  461.      *    Don't add the cookie if we're over the domain's limit. - FM
  462.      */
  463.     } else if (HTList_count(cookie_list) > 50) {
  464.     CTRACE(tfp, "store_cookie: Domain's cookie limit exceeded!  Rejecting cookie.\n");
  465.     freeCookie(co);
  466.     co = NULL;
  467.  
  468.     /*
  469.      *    Don't add the cookie if we're over the total cookie limit. - FM
  470.      */
  471.     } else if (total_cookies > 500) {
  472.     CTRACE(tfp, "store_cookie: Total cookie limit exceeded!  Rejecting cookie.\n");
  473.     freeCookie(co);
  474.     co = NULL;
  475.  
  476.     /*
  477.      *    If it's a replacement for a cookie that had not expired,
  478.      *    and never allow has not been set, add it again without
  479.      *    confirmation. - FM
  480.      */
  481.     } else if ((Replacement == TRUE && de) && de->bv != REJECT_ALWAYS) {
  482.     HTList_insertObjectAt(cookie_list, co, pos);
  483.     total_cookies++;
  484.  
  485.     /*
  486.      *    Get confirmation if we need it, and add cookie
  487.      *    if confirmed or 'allow' is set to always. - FM
  488.      */
  489.     } else if (HTConfirmCookie(de, hostname,
  490.                    co->domain, co->path, co->name, co->value)) {
  491.     HTList_insertObjectAt(cookie_list, co, pos);
  492.     total_cookies++;
  493.     } else {
  494.     freeCookie(co);
  495.     co = NULL;
  496.     }
  497. }
  498.  
  499. /*
  500. **  Scan a domain's cookie_list for any cookies we should
  501. **  include in a Cookie: request header. - AK & FM
  502. */
  503. PRIVATE char * scan_cookie_sublist ARGS6(
  504.     CONST char *,    hostname,
  505.     CONST char *,    path,
  506.     int,        port,
  507.     HTList *,    sublist,
  508.     char *,     header,
  509.     BOOL,        secure)
  510. {
  511.     HTList *hl = sublist, *next = NULL;
  512.     cookie *co;
  513.     time_t now = time(NULL);
  514.     int len = 0;
  515.     char crlftab[8];
  516.  
  517.     sprintf(crlftab, "%c%c%c", CR, LF, '\t');
  518.     while (hl) {
  519.     co = (cookie *)hl->object;
  520.     next = hl->next;
  521.  
  522.     if (co) {
  523.         CTRACE(tfp, "Checking cookie %lx %s=%s\n",
  524.                 (long)hl,
  525.                 (co->name ? co->name : "(no name)"),
  526.                 (co->value ? co->value : "(no value)"));
  527.         CTRACE(tfp, "%s %s %d %s %s %d%s\n",
  528.                 hostname,
  529.                 (co->domain ? co->domain : "(no domain)"),
  530.                 host_matches(hostname, co->domain),
  531.                 path, co->path, ((co->pathlen > 0) ?
  532.               strncmp(path, co->path, co->pathlen) : 0),
  533.                 ((co->flags & COOKIE_FLAG_SECURE) ?
  534.                            " secure" : ""));
  535.     }
  536.     /*
  537.      *  Check if this cookie has expired, and if so, delete it.
  538.      */
  539.     if (((co) && (co->flags & COOKIE_FLAG_EXPIRES_SET)) &&
  540.         co->expires < now) {
  541.         HTList_removeObject(sublist, co);
  542.         freeCookie(co);
  543.         co = NULL;
  544.         total_cookies--;
  545.     }
  546.  
  547.     /*
  548.      *  Check if we have a unexpired match, and handle if we do.
  549.      */
  550.     if (((co != NULL) &&
  551.          host_matches(hostname, co->domain)) &&
  552.         (co->pathlen == 0 || !strncmp(path, co->path, co->pathlen))) {
  553.         /*
  554.          *    Skip if the secure flag is set and we don't have
  555.          *    a secure connection.  HTTP.c presently treats only
  556.          *    SSL connections as secure. - FM
  557.          */
  558.         if ((co->flags & COOKIE_FLAG_SECURE) && secure == FALSE) {
  559.         hl = next;
  560.         continue;
  561.         }
  562.  
  563.         /*
  564.          *    Skip if we have a port list and the
  565.          *    current port is not listed. - FM
  566.          */
  567.         if (co->PortList && !port_matches(port, co->PortList)) {
  568.         hl = next;
  569.         continue;
  570.         }
  571.  
  572.         /*
  573.          *    Start or append to the request header.
  574.          */
  575.         if (header == NULL) {
  576.         if (co->version > 0) {
  577.             /*
  578.              *    For Version 1 (or greater) cookies,
  579.              *    the version number goes before the
  580.              *    first cookie.
  581.              */
  582.             char version[16];
  583.             sprintf(version, "$Version=\"%d\"; ", co->version);
  584.             StrAllocCopy(header, version);
  585.             len += strlen(header);
  586.         }
  587.         } else {
  588.         /*
  589.          *  There's already cookie data there, so add
  590.          *  a separator (always use a semi-colon for
  591.          *  "backward compatibility"). - FM
  592.          */
  593.         StrAllocCat(header, "; ");
  594.         /*
  595.          *  Check if we should fold the header. - FM
  596.          */
  597.         if (len > 800) {
  598.             StrAllocCat(header, crlftab);
  599.             len = 0;
  600.         }
  601.         }
  602.         /*
  603.          *    Include the cookie name=value pair.
  604.          */
  605.         StrAllocCat(header, co->name);
  606.         StrAllocCat(header, "=");
  607.         if (co->quoted) {
  608.         StrAllocCat(header, "\"");
  609.         len++;
  610.         }
  611.         StrAllocCat(header, co->value);
  612.         if (co->quoted) {
  613.         StrAllocCat(header, "\"");
  614.         len++;
  615.         }
  616.         len += (strlen(co->name) + strlen(co->value) + 1);
  617.         /*
  618.          *    For Version 1 (or greater) cookies, add
  619.          *    $PATH, $PORT and/or $DOMAIN attributes for
  620.          *    the cookie if they were specified via a
  621.          *    server reply header. - FM
  622.          */
  623.         if (co->version > 0) {
  624.         if (co->path && (co->flags & COOKIE_FLAG_PATH_SET)) {
  625.             /*
  626.              *    Append the path attribute. - FM
  627.              */
  628.             StrAllocCat(header, "; $Path=\"");
  629.             StrAllocCat(header, co->path);
  630.             StrAllocCat(header, "\"");
  631.             len += (strlen(co->path) + 10);
  632.         }
  633.         if (co->PortList && isdigit((unsigned char)*co->PortList)) {
  634.             /*
  635.              *    Append the port attribute. - FM
  636.              */
  637.             StrAllocCat(header, "; $Port=\"");
  638.             StrAllocCat(header, co->PortList);
  639.             StrAllocCat(header, "\"");
  640.             len += (strlen(co->PortList) + 10);
  641.         }
  642.         if (co->domain && (co->flags & COOKIE_FLAG_DOMAIN_SET)) {
  643.             /*
  644.              *    Append the domain attribute. - FM
  645.              */
  646.             StrAllocCat(header, "; $Domain=\"");
  647.             StrAllocCat(header, co->domain);
  648.             StrAllocCat(header, "\"");
  649.             len += (strlen(co->domain) + 12);
  650.         }
  651.         }
  652.     }
  653.     hl = next;
  654.     }
  655.  
  656.     return(header);
  657. }
  658.  
  659. /*
  660. **  Process potentially concatenated Set-Cookie2 and/or Set-Cookie
  661. **  headers. - FM
  662. */
  663. PRIVATE void LYProcessSetCookies ARGS6(
  664.     CONST char *,    SetCookie,
  665.     CONST char *,    SetCookie2,
  666.     CONST char *,    address,
  667.     char *,     hostname,
  668.     char *,     path,
  669.     int,        port)
  670. {
  671.     CONST char *p, *attr_start, *attr_end, *value_start, *value_end;
  672.     HTList *CombinedCookies = NULL, *cl = NULL;
  673.     cookie *cur_cookie = NULL, *co = NULL;
  674.     int length = 0, url_type = 0;
  675.     int NumCookies = 0;
  676.     BOOL MaxAgeAttrSet = FALSE;
  677.     BOOL Quoted = FALSE;
  678.  
  679.     if (!(SetCookie && *SetCookie) &&
  680.     !(SetCookie2 && *SetCookie2)) {
  681.     /*
  682.      *  Yuk!  Garbage in, so nothing out. - FM
  683.      */
  684.     return;
  685.     }
  686.  
  687.     /*
  688.      *    If we have both Set-Cookie and Set-Cookie2 headers.
  689.      *    process the Set-Cookie2 header.  Otherwise, process
  690.      *    whichever of the two headers we do have.  Note that
  691.      *    if more than one instance of a valued attribute for
  692.      *    the same cookie is encountered, the value for the
  693.      *    first instance is retained.  We only accept up to 50
  694.      *    cookies from the header, and only if a cookie's values
  695.      *    do not exceed the 4096 byte limit on overall size. - FM
  696.      */
  697.     CombinedCookies = HTList_new();
  698.  
  699.     /*
  700.      *    Process the Set-Cookie2 header, if present and not zero-length,
  701.      *    adding each cookie to the CombinedCookies list. - FM
  702.      */
  703.     p = (SetCookie2 ? SetCookie2 : "");
  704.     if (SetCookie && *p) {
  705.     CTRACE(tfp, "LYProcessSetCookies: Using Set-Cookie2 header.\n");
  706.     }
  707.     while (NumCookies <= 50 && *p) {
  708.     attr_start = attr_end = value_start = value_end = NULL;
  709.     p = LYSkipCBlanks(p);
  710.     /*
  711.      *  Get the attribute name.
  712.      */
  713.     attr_start = p;
  714.     while (*p != '\0' && !isspace((unsigned char)*p) &&
  715.            *p != '=' && *p != ';' && *p != ',')
  716.         p++;
  717.     attr_end = p;
  718.     p = LYSkipCBlanks(p);
  719.  
  720.     /*
  721.      *  Check for an '=' delimiter, or an 'expires' name followed
  722.      *  by white, since Netscape's bogus parser doesn't require
  723.      *  an '=' delimiter, and 'expires' attributes are being
  724.      *  encountered without them.  These shouldn't be in a
  725.      *  Set-Cookie2 header, but we'll assume it's an expires
  726.      *  attribute rather a cookie with that name, since the
  727.      *  attribute mistake rather than name mistake seems more
  728.      *  likely to be made by providers. - FM
  729.      */
  730.     if (*p == '=' ||
  731.          !strncasecomp(attr_start, "Expires", 7)) {
  732.         /*
  733.          *    Get the value string.
  734.          */
  735.         if (*p == '=') {
  736.         p++;
  737.         }
  738.         p = LYSkipCBlanks(p);
  739.         /*
  740.          *    Hack alert!  We must handle Netscape-style cookies with
  741.          *        "Expires=Mon, 01-Jan-96 13:45:35 GMT" or
  742.          *        "Expires=Mon,  1 Jan 1996 13:45:35 GMT".
  743.          *    No quotes, but there are spaces.  Argh...
  744.          *    Anyway, we know it will have at least 3 space separators
  745.          *    within it, and two dashes or two more spaces, so this code
  746.          *    looks for a space after the 5th space separator or dash to
  747.          *    mark the end of the value. - FM
  748.          */
  749.         if ((attr_end - attr_start) == 7 &&
  750.         !strncasecomp(attr_start, "Expires", 7)) {
  751.         int spaces = 6;
  752.         value_start = p;
  753.         if (isdigit((unsigned char)*p)) {
  754.             /*
  755.              *    No alphabetic day field. - FM
  756.              */
  757.             spaces--;
  758.         } else {
  759.             /*
  760.              *    Skip the alphabetic day field. - FM
  761.              */
  762.             while (*p != '\0' && isalpha((unsigned char)*p)) {
  763.             p++;
  764.             }
  765.             while (*p == ',' || isspace((unsigned char)*p)) {
  766.             p++;
  767.             }
  768.             spaces--;
  769.         }
  770.         while (*p != '\0' && *p != ';' && *p != ',' && spaces) {
  771.             p++;
  772.             if (isspace((unsigned char)*p)) {
  773.             while (isspace((unsigned char)*(p + 1)))
  774.                 p++;
  775.             spaces--;
  776.             } else if (*p == '-') {
  777.             spaces--;
  778.             }
  779.         }
  780.         value_end = p;
  781.         /*
  782.          *    Hack Alert!  The port attribute can take a
  783.          *    comma separated list of numbers as a value,
  784.          *    and such values should be quoted, but if
  785.          *    not, make sure we don't treat a number in
  786.          *    the list as the start of a new cookie. - FM
  787.          */
  788.         } else if ((attr_end - attr_start) == 4 &&
  789.                !strncasecomp(attr_start, "port", 4) &&
  790.                isdigit((unsigned char)*p)) {
  791.         /*
  792.          *  The value starts as an unquoted number.
  793.          */
  794.         CONST char *cp, *cp1;
  795.         value_start = p;
  796.         while (1) {
  797.             while (isdigit((unsigned char)*p))
  798.             p++;
  799.             value_end = p;
  800.             p = LYSkipCBlanks(p);
  801.             if (*p == '\0' || *p == ';')
  802.             break;
  803.             if (*p == ',') {
  804.             cp = LYSkipCBlanks(p + 1);
  805.             if (*cp != '\0' && isdigit((unsigned char)*cp)) {
  806.                 cp1 = cp;
  807.                 while (isdigit((unsigned char)*cp1))
  808.                 cp1++;
  809.                 cp1 = LYSkipCBlanks(cp1);
  810.                 if (*cp1 == '\0' || *cp1 == ',' || *cp1 == ';') {
  811.                 p = cp;
  812.                 continue;
  813.                 }
  814.             }
  815.             }
  816.             while (*p != '\0' && *p != ';' && *p != ',')
  817.             p++;
  818.             value_end = p;
  819.             /*
  820.              *    Trim trailing spaces.
  821.              */
  822.             if ((value_end > value_start) &&
  823.             isspace((unsigned char)*(value_end - 1))) {
  824.             value_end--;
  825.             while ((value_end > (value_start + 1)) &&
  826.                    isspace((unsigned char)*value_end) &&
  827.                    isspace((unsigned char)*(value_end - 1))) {
  828.                 value_end--;
  829.             }
  830.             }
  831.             break;
  832.         }
  833.         } else if (*p == '"') {
  834.         /*
  835.          *  It's a quoted string.
  836.          */
  837.         p++;
  838.         value_start = p;
  839.         while (*p != '\0' && *p != '"')
  840.             p++;
  841.         value_end = p;
  842.         if (*p == '"')
  843.             p++;
  844.         } else {
  845.         /*
  846.          *  Otherwise, it's an unquoted string.
  847.          */
  848.         value_start = p;
  849.         while (*p != '\0' && *p != ';' && *p != ',')
  850.             p++;
  851.         value_end = p;
  852.         /*
  853.          *  Trim trailing spaces.
  854.          */
  855.         if ((value_end > value_start) &&
  856.             isspace((unsigned char)*(value_end - 1))) {
  857.             value_end--;
  858.             while ((value_end > (value_start + 1)) &&
  859.                isspace((unsigned char)*value_end) &&
  860.                isspace((unsigned char)*(value_end - 1))) {
  861.             value_end--;
  862.             }
  863.         }
  864.         }
  865.     }
  866.  
  867.     /*
  868.      *  Check for a separator character, and skip it.
  869.      */
  870.     if (*p == ';' || *p == ',')
  871.         p++;
  872.  
  873.     /*
  874.      *  Now, we can handle this attribute/value pair.
  875.      */
  876.     if (attr_end > attr_start) {
  877.         int len = (attr_end - attr_start);
  878.         BOOLEAN known_attr = NO;
  879.         char *value = NULL;
  880.  
  881.         if (value_end > value_start) {
  882.         int value_len = (value_end - value_start);
  883.  
  884.         if (value_len > 4096) {
  885.             value_len = 4096;
  886.         }
  887.         value = (char *)calloc(1, value_len + 1);
  888.         if (value == NULL)
  889.             outofmem(__FILE__, "LYProcessSetCookies");
  890.         LYstrncpy(value, value_start, value_len);
  891.         }
  892.         if (len == 6 && !strncasecomp(attr_start, "secure", 6)) {
  893.         if (value == NULL) {
  894.             known_attr = YES;
  895.             if (cur_cookie != NULL) {
  896.             cur_cookie->flags |= COOKIE_FLAG_SECURE;
  897.             }
  898.         } else {
  899.             /*
  900.              *    If secure has a value, assume someone
  901.              *    misused it as cookie name. - FM
  902.              */
  903.             known_attr = NO;
  904.         }
  905.         } else if (len == 7 && !strncasecomp(attr_start, "discard", 7)) {
  906.         if (value == NULL) {
  907.             known_attr = YES;
  908.             if (cur_cookie != NULL) {
  909.             cur_cookie->flags |= COOKIE_FLAG_DISCARD;
  910.             }
  911.         } else {
  912.             /*
  913.              *    If discard has a value, assume someone
  914.              *    used it as a cookie name. - FM
  915.              */
  916.             known_attr = NO;
  917.         }
  918.         } else if (len == 7 && !strncasecomp(attr_start, "comment", 7)) {
  919.         known_attr = YES;
  920.         if (cur_cookie != NULL && value &&
  921.             /*
  922.              *    Don't process a repeat comment. - FM
  923.              */
  924.             cur_cookie->comment == NULL) {
  925.             StrAllocCopy(cur_cookie->comment, value);
  926.             length += strlen(cur_cookie->comment);
  927.         }
  928.         } else if (len == 10 && !strncasecomp(attr_start,
  929.                           "commentURL", 10)) {
  930.         known_attr = YES;
  931.         if (cur_cookie != NULL && value &&
  932.             /*
  933.              *    Don't process a repeat commentURL. - FM
  934.              */
  935.             cur_cookie->commentURL == NULL) {
  936.             /*
  937.              *    We should get only absolute URLs as
  938.              *    values, but will resolve versus the
  939.              *    request's URL just in case. - FM
  940.              */
  941.             cur_cookie->commentURL = HTParse(value,
  942.                              address,
  943.                              PARSE_ALL);
  944.             /*
  945.              *    Accept only URLs for http or https servers. - FM
  946.              */
  947.             if ((url_type = is_url(cur_cookie->commentURL)) &&
  948.             (url_type == HTTP_URL_TYPE ||
  949.              url_type == HTTPS_URL_TYPE)) {
  950.             length += strlen(cur_cookie->commentURL);
  951.             } else {
  952.             CTRACE(tfp, "LYProcessSetCookies: Rejecting commentURL value '%s'\n",
  953.                     cur_cookie->commentURL);
  954.             FREE(cur_cookie->commentURL);
  955.             }
  956.         }
  957.         } else if (len == 6 && !strncasecomp(attr_start, "domain", 6)) {
  958.         known_attr = YES;
  959.         if (cur_cookie != NULL && value &&
  960.             /*
  961.              *    Don't process a repeat domain. - FM
  962.              */
  963.             !(cur_cookie->flags & COOKIE_FLAG_DOMAIN_SET)) {
  964.             length -= strlen(cur_cookie->domain);
  965.             /*
  966.              *    If the value does not have a lead dot,
  967.              *    but does have an embedded dot, and is
  968.              *    not an exact match to the hostname, nor
  969.              *    is a numeric IP address, add a lead dot.
  970.              *    Otherwise, use the value as is. - FM
  971.              */
  972.             if (value[0] != '.' && value[0] != '\0' &&
  973.             value[1] != '\0' && strcmp(value, hostname)) {
  974.             char *ptr = strchr(value, '.');
  975.             if (ptr != NULL && ptr[1] != '\0') {
  976.                 ptr = value;
  977.                 while (*ptr == '.' ||
  978.                    isdigit((unsigned char)*ptr))
  979.                 ptr++;
  980.                 if (*ptr != '\0') {
  981.                 CTRACE(tfp,
  982.            "LYProcessSetCookies: Adding lead dot for domain value '%s'\n",
  983.                         value);
  984.                 StrAllocCopy(cur_cookie->domain, ".");
  985.                 StrAllocCat(cur_cookie->domain, value);
  986.                 } else {
  987.                 StrAllocCopy(cur_cookie->domain, value);
  988.                 }
  989.             } else {
  990.                 StrAllocCopy(cur_cookie->domain, value);
  991.             }
  992.             } else {
  993.             StrAllocCopy(cur_cookie->domain, value);
  994.             }
  995.             length += strlen(cur_cookie->domain);
  996.             cur_cookie->flags |= COOKIE_FLAG_DOMAIN_SET;
  997.         }
  998.         } else if (len == 4 && !strncasecomp(attr_start, "path", 4)) {
  999.         known_attr = YES;
  1000.         if (cur_cookie != NULL && value &&
  1001.             /*
  1002.              *    Don't process a repeat path. - FM
  1003.              */
  1004.             !(cur_cookie->flags & COOKIE_FLAG_PATH_SET)) {
  1005.             length -= strlen(cur_cookie->path);
  1006.             StrAllocCopy(cur_cookie->path, value);
  1007.             length += (cur_cookie->pathlen = strlen(cur_cookie->path));
  1008.             cur_cookie->flags |= COOKIE_FLAG_PATH_SET;
  1009.         }
  1010.         } else if (len == 4 && !strncasecomp(attr_start, "port", 4)) {
  1011.         if (cur_cookie != NULL && value &&
  1012.             /*
  1013.              *    Don't process a repeat port. - FM
  1014.              */
  1015.             cur_cookie->PortList == NULL) {
  1016.             char *cp = value;
  1017.             while ((*cp != '\0') &&
  1018.                (isdigit((unsigned char)*cp) ||
  1019.                 *cp == ',' || *cp == ' ')) {
  1020.             cp++;
  1021.             }
  1022.             if (*cp == '\0') {
  1023.             StrAllocCopy(cur_cookie->PortList, value);
  1024.             length += strlen(cur_cookie->PortList);
  1025.             known_attr = YES;
  1026.             } else {
  1027.             known_attr = NO;
  1028.             }
  1029.         } else if (cur_cookie != NULL) {
  1030.             /*
  1031.              *    Don't process a repeat port. - FM
  1032.              */
  1033.             if (cur_cookie->PortList == NULL) {
  1034.             char temp[256];
  1035.             sprintf(temp, "%d", port);
  1036.             StrAllocCopy(cur_cookie->PortList, temp);
  1037.             length += strlen(cur_cookie->PortList);
  1038.             }
  1039.             known_attr = YES;
  1040.         }
  1041.         } else if (len == 7 && !strncasecomp(attr_start, "version", 7)) {
  1042.         known_attr = YES;
  1043.         if (cur_cookie != NULL && value &&
  1044.             /*
  1045.              *    Don't process a repeat version. - FM
  1046.              */
  1047.             cur_cookie->version < 1) {
  1048.             int temp = strtol(value, NULL, 10);
  1049.             if (errno != -ERANGE) {
  1050.             cur_cookie->version = temp;
  1051.             }
  1052.         }
  1053.         } else if (len == 7 && !strncasecomp(attr_start, "max-age", 7)) {
  1054.         known_attr = YES;
  1055.         if (cur_cookie != NULL && value &&
  1056.             /*
  1057.              *    Don't process a repeat max-age. - FM
  1058.              */
  1059.             !MaxAgeAttrSet) {
  1060.             int temp = strtol(value, NULL, 10);
  1061.             cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
  1062.             if (errno == -ERANGE) {
  1063.             cur_cookie->expires = (time_t)0;
  1064.             } else {
  1065.             cur_cookie->expires = (time(NULL) + temp);
  1066.             CTRACE(tfp, "LYSetCookie: expires %ld, %s",
  1067.                     (long) cur_cookie->expires,
  1068.                     ctime(&cur_cookie->expires));
  1069.             }
  1070.             MaxAgeAttrSet = TRUE;
  1071.         }
  1072.         } else if (len == 7 && !strncasecomp(attr_start, "expires", 7)) {
  1073.         /*
  1074.          *  Convert an 'expires' attribute value if we haven't
  1075.          *  received a 'max-age'.  Note that 'expires' should not
  1076.          *  be used in Version 1 cookies, but it might be used for
  1077.          *  "backward compatibility", and, in turn, ill-informed
  1078.          *  people surely would start using it instead of, rather
  1079.          *  than in addition to, 'max-age'. - FM
  1080.          */
  1081.         known_attr = YES;
  1082.         if ((cur_cookie != NULL && !MaxAgeAttrSet) &&
  1083.              !(cur_cookie->flags & COOKIE_FLAG_EXPIRES_SET)) {
  1084.             known_attr = YES;
  1085.             if (value) {
  1086.             cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
  1087.             cur_cookie->expires = LYmktime(value, FALSE);
  1088.             if (cur_cookie->expires > 0) {
  1089.                 CTRACE(tfp, "LYSetCookie: expires %ld, %s",
  1090.                     (long) cur_cookie->expires,
  1091.                     ctime(&cur_cookie->expires));
  1092.             }
  1093.             }
  1094.         }
  1095.         }
  1096.  
  1097.         /*
  1098.          *    If none of the above comparisons succeeded, and we have
  1099.          *    a value, then we have an unknown pair of the form 'foo=bar',
  1100.          *    which means it's time to create a new cookie.  If we don't
  1101.          *    have a non-zero-length value, assume it's an error or a
  1102.          *    new, unknown attribute which doesn't take a value, and
  1103.          *    ignore it. - FM
  1104.          */
  1105.         if (!known_attr && value_end > value_start) {
  1106.         /*
  1107.          *  If we've started a cookie, and it's not too big,
  1108.          *  save it in the CombinedCookies list. - FM
  1109.          */
  1110.         if (length <= 4096 && cur_cookie != NULL) {
  1111.             /*
  1112.              *    Assume version 1 if not set to that or higher. - FM
  1113.              */
  1114.             if (cur_cookie->version < 1) {
  1115.             cur_cookie->version = 1;
  1116.             }
  1117.             HTList_appendObject(CombinedCookies, cur_cookie);
  1118.         } else if (cur_cookie != NULL) {
  1119.             CTRACE(tfp,
  1120.             "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
  1121.                 (cur_cookie->name ?
  1122.                  cur_cookie->name : "[no name]"),
  1123.                 (cur_cookie->value ?
  1124.                  cur_cookie->value : "[no value]"));
  1125.             CTRACE(tfp,
  1126.             "                     due to excessive length!\n");
  1127.             freeCookie(cur_cookie);
  1128.             cur_cookie = NULL;
  1129.         }
  1130.         /*
  1131.          *  Start a new cookie. - FM
  1132.          */
  1133.         cur_cookie = newCookie();
  1134.         length = 0;
  1135.         NumCookies++;
  1136.         MemAllocCopy(&(cur_cookie->name), attr_start, attr_end);
  1137.         length += strlen(cur_cookie->name);
  1138.         MemAllocCopy(&(cur_cookie->value), value_start, value_end);
  1139.         length += strlen(cur_cookie->value);
  1140.         StrAllocCopy(cur_cookie->domain, hostname);
  1141.         length += strlen(cur_cookie->domain);
  1142.         StrAllocCopy(cur_cookie->path, path);
  1143.         length += (cur_cookie->pathlen = strlen(cur_cookie->path));
  1144.         cur_cookie->port = port;
  1145.         MaxAgeAttrSet = FALSE;
  1146.         cur_cookie->quoted = TRUE;
  1147.         }
  1148.         FREE(value);
  1149.     }
  1150.     }
  1151.  
  1152.     /*
  1153.      *    Add any final SetCookie2 cookie to the CombinedCookie list
  1154.      *    if we are within the length limit. - FM
  1155.      */
  1156.     if (NumCookies <= 50 && length <= 4096 && cur_cookie != NULL) {
  1157.     if (cur_cookie->version < 1) {
  1158.         cur_cookie->version = 1;
  1159.     }
  1160.     HTList_appendObject(CombinedCookies, cur_cookie);
  1161.     } else if (cur_cookie != NULL) {
  1162.     CTRACE(tfp, "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
  1163.             (cur_cookie->name ? cur_cookie->name : "[no name]"),
  1164.             (cur_cookie->value ? cur_cookie->value : "[no value]"));
  1165.     CTRACE(tfp, "                     due to excessive %s%s%s\n",
  1166.             (length > 4096 ? "length" : ""),
  1167.             (length > 4096 && NumCookies > 50 ? " and " : ""),
  1168.             (NumCookies > 50 ? "number!\n" : "!\n"));
  1169.     freeCookie(cur_cookie);
  1170.     cur_cookie = NULL;
  1171.     }
  1172.  
  1173.     /*
  1174.      *    Process the Set-Cookie header, if no non-zero-length Set-Cookie2
  1175.      *    header was present. - FM
  1176.      */
  1177.     length = 0;
  1178.     NumCookies = 0;
  1179.     cur_cookie = NULL;
  1180.     p = ((SetCookie && !(SetCookie2 && *SetCookie2)) ? SetCookie : "");
  1181.     if (SetCookie2 && *p) {
  1182.     CTRACE(tfp, "LYProcessSetCookies: Using Set-Cookie header.\n");
  1183.     }
  1184.     while (NumCookies <= 50 && *p) {
  1185.     attr_start = attr_end = value_start = value_end = NULL;
  1186.     p = LYSkipCBlanks(p);
  1187.     /*
  1188.      *  Get the attribute name.
  1189.      */
  1190.     attr_start = p;
  1191.     while (*p != '\0' && !isspace((unsigned char)*p) &&
  1192.            *p != '=' && *p != ';' && *p != ',')
  1193.         p++;
  1194.     attr_end = p;
  1195.     p = LYSkipCBlanks(p);
  1196.  
  1197.     /*
  1198.      *  Check for an '=' delimiter, or an 'expires' name followed
  1199.      *  by white, since Netscape's bogus parser doesn't require
  1200.      *  an '=' delimiter, and 'expires' attributes are being
  1201.      *  encountered without them. - FM
  1202.      */
  1203.     if (*p == '=' ||
  1204.          !strncasecomp(attr_start, "Expires", 7)) {
  1205.         /*
  1206.          *    Get the value string.
  1207.          */
  1208.         if (*p == '=') {
  1209.         p++;
  1210.         }
  1211.         p = LYSkipCBlanks(p);
  1212.         /*
  1213.          *    Hack alert!  We must handle Netscape-style cookies with
  1214.          *        "Expires=Mon, 01-Jan-96 13:45:35 GMT" or
  1215.          *        "Expires=Mon,  1 Jan 1996 13:45:35 GMT".
  1216.          *    No quotes, but there are spaces.  Argh...
  1217.          *    Anyway, we know it will have at least 3 space separators
  1218.          *    within it, and two dashes or two more spaces, so this code
  1219.          *    looks for a space after the 5th space separator or dash to
  1220.          *    mark the end of the value. - FM
  1221.          */
  1222.         if ((attr_end - attr_start) == 7 &&
  1223.         !strncasecomp(attr_start, "Expires", 7)) {
  1224.         int spaces = 6;
  1225.         value_start = p;
  1226.         if (isdigit((unsigned char)*p)) {
  1227.             /*
  1228.              *    No alphabetic day field. - FM
  1229.              */
  1230.             spaces--;
  1231.         } else {
  1232.             /*
  1233.              *    Skip the alphabetic day field. - FM
  1234.              */
  1235.             while (*p != '\0' && isalpha((unsigned char)*p)) {
  1236.             p++;
  1237.             }
  1238.             while (*p == ',' || isspace((unsigned char)*p)) {
  1239.             p++;
  1240.             }
  1241.             spaces--;
  1242.         }
  1243.         while (*p != '\0' && *p != ';' && *p != ',' && spaces) {
  1244.             p++;
  1245.             if (isspace((unsigned char)*p)) {
  1246.             while (isspace((unsigned char)*(p + 1)))
  1247.                 p++;
  1248.             spaces--;
  1249.             } else if (*p == '-') {
  1250.             spaces--;
  1251.             }
  1252.         }
  1253.         value_end = p;
  1254.         /*
  1255.          *    Hack Alert!  The port attribute can take a
  1256.          *    comma separated list of numbers as a value,
  1257.          *    and such values should be quoted, but if
  1258.          *    not, make sure we don't treat a number in
  1259.          *    the list as the start of a new cookie. - FM
  1260.          */
  1261.         } else if ((attr_end - attr_start) == 4 &&
  1262.                !strncasecomp(attr_start, "port", 4) &&
  1263.                isdigit((unsigned char)*p)) {
  1264.         /*
  1265.          *  The value starts as an unquoted number.
  1266.          */
  1267.         CONST char *cp, *cp1;
  1268.         value_start = p;
  1269.         while (1) {
  1270.             while (isdigit((unsigned char)*p))
  1271.             p++;
  1272.             value_end = p;
  1273.             p = LYSkipCBlanks(p);
  1274.             if (*p == '\0' || *p == ';')
  1275.             break;
  1276.             if (*p == ',') {
  1277.             cp = LYSkipCBlanks(p + 1);
  1278.             if (*cp != '\0' && isdigit((unsigned char)*cp)) {
  1279.                 cp1 = cp;
  1280.                 while (isdigit((unsigned char)*cp1))
  1281.                 cp1++;
  1282.                 cp1 = LYSkipCBlanks(cp1);
  1283.                 if (*cp1 == '\0' || *cp1 == ',' || *cp1 == ';') {
  1284.                 p = cp;
  1285.                 continue;
  1286.                 }
  1287.             }
  1288.             }
  1289.             while (*p != '\0' && *p != ';' && *p != ',')
  1290.             p++;
  1291.             value_end = p;
  1292.             /*
  1293.              *    Trim trailing spaces.
  1294.              */
  1295.             if ((value_end > value_start) &&
  1296.             isspace((unsigned char)*(value_end - 1))) {
  1297.             value_end--;
  1298.             while ((value_end > (value_start + 1)) &&
  1299.                    isspace((unsigned char)*value_end) &&
  1300.                    isspace((unsigned char)*(value_end - 1))) {
  1301.                 value_end--;
  1302.             }
  1303.             }
  1304.             break;
  1305.         }
  1306.         } else if (*p == '"') {
  1307.         /*
  1308.          *  It's a quoted string.
  1309.          */
  1310.         p++;
  1311.         value_start = p;
  1312.         while (*p != '\0' && *p != '"')
  1313.             p++;
  1314.         value_end = p;
  1315.         if (*p == '"')
  1316.             p++;
  1317.         Quoted = TRUE;
  1318.         } else {
  1319.         /*
  1320.          *  Otherwise, it's an unquoted string.
  1321.          */
  1322.         value_start = p;
  1323.         while (*p != '\0' && *p != ';' && *p != ',')
  1324.             p++;
  1325.         value_end = p;
  1326.         /*
  1327.          *  Trim trailing spaces.
  1328.          */
  1329.         if ((value_end > value_start) &&
  1330.             isspace((unsigned char)*(value_end - 1))) {
  1331.             value_end--;
  1332.             while ((value_end > (value_start + 1)) &&
  1333.                isspace((unsigned char)*value_end) &&
  1334.                isspace((unsigned char)*(value_end - 1))) {
  1335.             value_end--;
  1336.             }
  1337.         }
  1338.         }
  1339.     }
  1340.  
  1341.     /*
  1342.      *  Check for a separator character, and skip it.
  1343.      */
  1344.     if (*p == ';' || *p == ',')
  1345.         p++;
  1346.  
  1347.     /*
  1348.      *  Now, we can handle this attribute/value pair.
  1349.      */
  1350.     if (attr_end > attr_start) {
  1351.         int len = (attr_end - attr_start);
  1352.         BOOLEAN known_attr = NO;
  1353.         char *value = NULL;
  1354.  
  1355.         if (value_end > value_start) {
  1356.         int value_len = (value_end - value_start);
  1357.  
  1358.         if (value_len > 4096) {
  1359.             value_len = 4096;
  1360.         }
  1361.         value = (char *)calloc(1, value_len + 1);
  1362.         if (value == NULL)
  1363.             outofmem(__FILE__, "LYProcessSetCookie");
  1364.         LYstrncpy(value, value_start, value_len);
  1365.         }
  1366.         if (len == 6 && !strncasecomp(attr_start, "secure", 6)) {
  1367.         if (value == NULL) {
  1368.             known_attr = YES;
  1369.             if (cur_cookie != NULL) {
  1370.             cur_cookie->flags |= COOKIE_FLAG_SECURE;
  1371.             }
  1372.         } else {
  1373.             /*
  1374.              *    If secure has a value, assume someone
  1375.              *    misused it as cookie name. - FM
  1376.              */
  1377.             known_attr = NO;
  1378.         }
  1379.         } else if (len == 7 && !strncasecomp(attr_start, "discard", 7)) {
  1380.         if (value == NULL) {
  1381.             known_attr = YES;
  1382.             if (cur_cookie != NULL) {
  1383.             cur_cookie->flags |= COOKIE_FLAG_DISCARD;
  1384.             }
  1385.         } else {
  1386.             /*
  1387.              *    If discard has a value, assume someone
  1388.              *    used it as a cookie name. - FM
  1389.              */
  1390.             known_attr = NO;
  1391.         }
  1392.         } else if (len == 7 && !strncasecomp(attr_start, "comment", 7)) {
  1393.         known_attr = YES;
  1394.         if (cur_cookie != NULL && value &&
  1395.             /*
  1396.              *    Don't process a repeat comment. - FM
  1397.              */
  1398.             cur_cookie->comment == NULL) {
  1399.             StrAllocCopy(cur_cookie->comment, value);
  1400.             length += strlen(cur_cookie->comment);
  1401.         }
  1402.         } else if (len == 10 && !strncasecomp(attr_start,
  1403.                           "commentURL", 10)) {
  1404.         known_attr = YES;
  1405.         if (cur_cookie != NULL && value &&
  1406.             /*
  1407.              *    Don't process a repeat commentURL. - FM
  1408.              */
  1409.             cur_cookie->commentURL == NULL) {
  1410.             /*
  1411.              *    We should get only absolute URLs as
  1412.              *    values, but will resolve versus the
  1413.              *    request's URL just in case. - FM
  1414.              */
  1415.             cur_cookie->commentURL = HTParse(value,
  1416.                              address,
  1417.                              PARSE_ALL);
  1418.             /*
  1419.              *    Accept only URLs for http or https servers. - FM
  1420.              */
  1421.             if ((url_type = is_url(cur_cookie->commentURL)) &&
  1422.             (url_type == HTTP_URL_TYPE ||
  1423.              url_type == HTTPS_URL_TYPE)) {
  1424.             length += strlen(cur_cookie->commentURL);
  1425.             } else {
  1426.             CTRACE(tfp, "LYProcessSetCookies: Rejecting commentURL value '%s'\n",
  1427.                     cur_cookie->commentURL);
  1428.             FREE(cur_cookie->commentURL);
  1429.             }
  1430.         }
  1431.         } else if (len == 6 && !strncasecomp(attr_start, "domain", 6)) {
  1432.         known_attr = YES;
  1433.         if (cur_cookie != NULL && value &&
  1434.             /*
  1435.              *    Don't process a repeat domain. - FM
  1436.              */
  1437.             !(cur_cookie->flags & COOKIE_FLAG_DOMAIN_SET)) {
  1438.             length -= strlen(cur_cookie->domain);
  1439.             /*
  1440.              *    If the value does not have a lead dot,
  1441.              *    but does have an embedded dot, and is
  1442.              *    not an exact match to the hostname, nor
  1443.              *    is a numeric IP address, add a lead dot.
  1444.              *    Otherwise, use the value as is. - FM
  1445.              */
  1446.             if (value[0] != '.' && value[0] != '\0' &&
  1447.             value[1] != '\0' && strcmp(value, hostname)) {
  1448.             char *ptr = strchr(value, '.');
  1449.             if (ptr != NULL && ptr[1] != '\0') {
  1450.                 ptr = value;
  1451.                 while (*ptr == '.' ||
  1452.                    isdigit((unsigned char)*ptr))
  1453.                 ptr++;
  1454.                 if (*ptr != '\0') {
  1455.                 CTRACE(tfp,
  1456.            "LYProcessSetCookies: Adding lead dot for domain value '%s'\n",
  1457.                         value);
  1458.                 StrAllocCopy(cur_cookie->domain, ".");
  1459.                 StrAllocCat(cur_cookie->domain, value);
  1460.                 } else {
  1461.                 StrAllocCopy(cur_cookie->domain, value);
  1462.                 }
  1463.             } else {
  1464.                 StrAllocCopy(cur_cookie->domain, value);
  1465.             }
  1466.             } else {
  1467.             StrAllocCopy(cur_cookie->domain, value);
  1468.             }
  1469.             length += strlen(cur_cookie->domain);
  1470.             cur_cookie->flags |= COOKIE_FLAG_DOMAIN_SET;
  1471.         }
  1472.         } else if (len == 4 && !strncasecomp(attr_start, "path", 4)) {
  1473.         known_attr = YES;
  1474.         if (cur_cookie != NULL && value &&
  1475.             /*
  1476.              *    Don't process a repeat path. - FM
  1477.              */
  1478.             !(cur_cookie->flags & COOKIE_FLAG_PATH_SET)) {
  1479.             length -= strlen(cur_cookie->path);
  1480.             StrAllocCopy(cur_cookie->path, value);
  1481.             length += (cur_cookie->pathlen = strlen(cur_cookie->path));
  1482.             cur_cookie->flags |= COOKIE_FLAG_PATH_SET;
  1483.         }
  1484.         } else if (len == 4 && !strncasecomp(attr_start, "port", 4)) {
  1485.         if (cur_cookie != NULL && value &&
  1486.             /*
  1487.              *    Don't process a repeat port. - FM
  1488.              */
  1489.             cur_cookie->PortList == NULL) {
  1490.             char *cp = value;
  1491.             while ((*cp != '\0') &&
  1492.                (isdigit((unsigned char)*cp) ||
  1493.                 *cp == ',' || *cp == ' ')) {
  1494.             cp++;
  1495.             }
  1496.             if (*cp == '\0') {
  1497.             StrAllocCopy(cur_cookie->PortList, value);
  1498.             length += strlen(cur_cookie->PortList);
  1499.             known_attr = YES;
  1500.             } else {
  1501.             known_attr = NO;
  1502.             }
  1503.         } else if (cur_cookie != NULL) {
  1504.             /*
  1505.              *    Don't process a repeat port. - FM
  1506.              */
  1507.             if (cur_cookie->PortList == NULL) {
  1508.             char temp[256];
  1509.             sprintf(temp, "%d", port);
  1510.             StrAllocCopy(cur_cookie->PortList, temp);
  1511.             length += strlen(cur_cookie->PortList);
  1512.             }
  1513.             known_attr = YES;
  1514.         }
  1515.         } else if (len == 7 && !strncasecomp(attr_start, "version", 7)) {
  1516.         known_attr = YES;
  1517.         if (cur_cookie != NULL && value &&
  1518.             /*
  1519.              *    Don't process a repeat version. - FM
  1520.              */
  1521.             cur_cookie->version < 0) {
  1522.             int temp = strtol(value, NULL, 10);
  1523.             if (errno != -ERANGE) {
  1524.             cur_cookie->version = temp;
  1525.             }
  1526.         }
  1527.         } else if (len == 7 && !strncasecomp(attr_start, "max-age", 7)) {
  1528.         known_attr = YES;
  1529.         if ((cur_cookie != NULL) && !MaxAgeAttrSet && value) {
  1530.             int temp = strtol(value, NULL, 10);
  1531.             cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
  1532.             if (errno == -ERANGE) {
  1533.             cur_cookie->expires = (time_t)0;
  1534.             } else {
  1535.             cur_cookie->expires = (time(NULL) + temp);
  1536.             }
  1537.             MaxAgeAttrSet = TRUE;
  1538.         }
  1539.         } else if (len == 7 && !strncasecomp(attr_start, "expires", 7)) {
  1540.         /*
  1541.          *  Convert an 'expires' attribute value if we haven't
  1542.          *  received a 'max-age'.  Note that 'expires' should not
  1543.          *  be used in Version 1 cookies, but it might be used for
  1544.          *  "backward compatibility", and, in turn, ill-informed
  1545.          *  people surely would start using it instead of, rather
  1546.          *  than in addition to, 'max-age'. - FM
  1547.          */
  1548.         known_attr = YES;
  1549.         if ((cur_cookie != NULL) && !(MaxAgeAttrSet) &&
  1550.             !(cur_cookie->flags & COOKIE_FLAG_EXPIRES_SET)) {
  1551.             if (value) {
  1552.             cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
  1553.             cur_cookie->expires = LYmktime(value, FALSE);
  1554.             }
  1555.         }
  1556.         }
  1557.  
  1558.         /*
  1559.          *    If none of the above comparisons succeeded, and we have
  1560.          *    a value, then we have an unknown pair of the form 'foo=bar',
  1561.          *    which means it's time to create a new cookie.  If we don't
  1562.          *    have a non-zero-length value, assume it's an error or a
  1563.          *    new, unknown attribute which doesn't take a value, and
  1564.          *    ignore it. - FM
  1565.          */
  1566.         if (!known_attr && value_end > value_start) {
  1567.         /*
  1568.          *  If we've started a cookie, and it's not too big,
  1569.          *  save it in the CombinedCookies list. - FM
  1570.          */
  1571.         if (length <= 4096 && cur_cookie != NULL) {
  1572.             /*
  1573.              *    If we had a Set-Cookie2 header, make sure
  1574.              *    the version is at least 1, and mark it for
  1575.              *    quoting. - FM
  1576.              */
  1577.             if (SetCookie2 != NULL) {
  1578.             if (cur_cookie->version < 1) {
  1579.                 cur_cookie->version = 1;
  1580.             }
  1581.             cur_cookie->quoted = TRUE;
  1582.             }
  1583.             HTList_appendObject(CombinedCookies, cur_cookie);
  1584.         } else if (cur_cookie != NULL) {
  1585.             CTRACE(tfp, "LYProcessSetCookies: Rejecting Set-Cookie: %s=%s\n",
  1586.                 (cur_cookie->name ?
  1587.                  cur_cookie->name : "[no name]"),
  1588.                 (cur_cookie->value ?
  1589.                  cur_cookie->value : "[no value]"));
  1590.             CTRACE(tfp, "                     due to excessive length!\n");
  1591.             freeCookie(cur_cookie);
  1592.             cur_cookie = NULL;
  1593.         }
  1594.         /*
  1595.          *  Start a new cookie. - FM
  1596.          */
  1597.         cur_cookie = newCookie();
  1598.         length = 0;
  1599.         MemAllocCopy(&(cur_cookie->name), attr_start, attr_end);
  1600.         length += strlen(cur_cookie->name);
  1601.         MemAllocCopy(&(cur_cookie->value), value_start, value_end);
  1602.         length += strlen(cur_cookie->value);
  1603.         StrAllocCopy(cur_cookie->domain, hostname);
  1604.         length += strlen(cur_cookie->domain);
  1605.         StrAllocCopy(cur_cookie->path, path);
  1606.         length += (cur_cookie->pathlen = strlen(cur_cookie->path));
  1607.         cur_cookie->port = port;
  1608.         MaxAgeAttrSet = FALSE;
  1609.         cur_cookie->quoted = Quoted;
  1610.         Quoted = FALSE;
  1611.         }
  1612.         FREE(value);
  1613.     }
  1614.     }
  1615.  
  1616.     /*
  1617.      *    Handle the final Set-Cookie cookie if within length limit. - FM
  1618.      */
  1619.     if (NumCookies <= 50 && length <= 4096 && cur_cookie != NULL) {
  1620.     if (SetCookie2 != NULL) {
  1621.         if (cur_cookie->version < 1) {
  1622.         cur_cookie->version = 1;
  1623.         }
  1624.         cur_cookie->quoted = TRUE;
  1625.     }
  1626.     HTList_appendObject(CombinedCookies, cur_cookie);
  1627.     } else if (cur_cookie != NULL) {
  1628.     CTRACE(tfp, "LYProcessSetCookies: Rejecting Set-Cookie: %s=%s\n",
  1629.             (cur_cookie->name ? cur_cookie->name : "[no name]"),
  1630.             (cur_cookie->value ? cur_cookie->value : "[no value]"));
  1631.     CTRACE(tfp, "                     due to excessive %s%s%s\n",
  1632.             (length > 4096 ? "length" : ""),
  1633.             (length > 4096 && NumCookies > 50 ? " and " : ""),
  1634.             (NumCookies > 50 ? "number!\n" : "!\n"));
  1635.     freeCookie(cur_cookie);
  1636.     cur_cookie = NULL;
  1637.     }
  1638.  
  1639.     /*
  1640.      *    OK, now we can actually store any cookies
  1641.      *    in the CombinedCookies list. - FM
  1642.      */
  1643.     cl = CombinedCookies;
  1644.     while (NULL != (co = (cookie *)HTList_nextObject(cl))) {
  1645.     CTRACE(tfp, "LYProcessSetCookie: attr=value pair: '%s=%s'\n",
  1646.                 (co->name ? co->name : "[no name]"),
  1647.                 (co->value ? co->value : "[no value]"));
  1648.     if (co->expires > 0) {
  1649.         CTRACE(tfp, "                    expires: %ld, %s\n",
  1650.                 (long)co->expires,
  1651.                 ctime(&co->expires));
  1652.     }
  1653.     if (!strncasecomp(address, "https:", 6) &&
  1654.         LYForceSSLCookiesSecure == TRUE &&
  1655.         !(co->flags & COOKIE_FLAG_SECURE)) {
  1656.         co->flags |= COOKIE_FLAG_SECURE;
  1657.         CTRACE(tfp, "                    Forced the 'secure' flag on.\n");
  1658.     }
  1659.     store_cookie(co, hostname, path);
  1660.     }
  1661.     HTList_delete(CombinedCookies);
  1662.     CombinedCookies = NULL;
  1663.  
  1664.     return;
  1665. }
  1666.  
  1667. /*
  1668. **  Entry function for handling Set-Cookie: and/or Set-Cookie2:
  1669. **  reply headers.   They may have been concatenated as comma
  1670. **  separated lists in HTTP.c or HTMIME.c. - FM
  1671. */
  1672. PUBLIC void LYSetCookie ARGS3(
  1673.     CONST char *,    SetCookie,
  1674.     CONST char *,    SetCookie2,
  1675.     CONST char *,    address)
  1676. {
  1677.     BOOL BadHeaders = FALSE;
  1678.     char *hostname = NULL, *path = NULL, *ptr;
  1679.     int port = 80;
  1680.  
  1681.     /*
  1682.      *    Get the hostname, port and path of the address, and report
  1683.      *    the Set-Cookie and/or Set-Cookie2 header(s) if trace mode is
  1684.      *    on, but set the cookie(s) only if LYSetCookies is TRUE. - FM
  1685.      */
  1686.     if (((hostname = HTParse(address, "", PARSE_HOST)) != NULL) &&
  1687.     (ptr = strchr(hostname, ':')) != NULL)    {
  1688.     /*
  1689.      *  Replace default port number.
  1690.      */
  1691.     *ptr = '\0';
  1692.     ptr++;
  1693.     port = atoi(ptr);
  1694.     } else if (!strncasecomp(address, "https:", 6)) {
  1695.     port = 443;
  1696.     }
  1697.     if (((path = HTParse(address, "",
  1698.              PARSE_PATH|PARSE_PUNCTUATION)) != NULL) &&
  1699.     (ptr = strrchr(path, '/')) != NULL) {
  1700.     if (ptr == path) {
  1701.         *(ptr+1) = '\0';    /* Leave a single '/' alone */
  1702.     } else {
  1703.         *ptr = '\0';
  1704.     }
  1705.     }
  1706.     if (!(SetCookie && *SetCookie) &&
  1707.     !(SetCookie2 && *SetCookie2)) {
  1708.     /*
  1709.      *  Yuk, something must have gone wrong in
  1710.      *  HTMIME.c or HTTP.c because both SetCookie
  1711.      *  and SetCookie2 are NULL or zero-length. - FM
  1712.      */
  1713.     BadHeaders = TRUE;
  1714.     }
  1715.     CTRACE(tfp, "LYSetCookie called with host '%s', path '%s',\n",
  1716.         (hostname ? hostname : ""),
  1717.         (path ? path : ""));
  1718.     if (SetCookie) {
  1719.     CTRACE(tfp, "    and Set-Cookie: '%s'\n",
  1720.              (SetCookie ? SetCookie : ""));
  1721.     }
  1722.     if (SetCookie2) {
  1723.     CTRACE(tfp, "    and Set-Cookie2: '%s'\n",
  1724.              (SetCookie2 ? SetCookie2 : ""));
  1725.     }
  1726.     if (LYSetCookies == FALSE || BadHeaders == TRUE) {
  1727.     CTRACE(tfp, "    Ignoring this Set-Cookie/Set-Cookie2 request.\n");
  1728.     }
  1729.  
  1730.     /*
  1731.      *    We're done if LYSetCookies is off or we have bad headers. - FM
  1732.      */
  1733.     if (LYSetCookies == FALSE || BadHeaders == TRUE) {
  1734.     FREE(hostname);
  1735.     FREE(path);
  1736.     return;
  1737.     }
  1738.  
  1739.     /*
  1740.      *    Process the header(s).
  1741.      */
  1742.     LYProcessSetCookies(SetCookie, SetCookie2, address, hostname, path, port);
  1743.     FREE(hostname);
  1744.     FREE(path);
  1745.     return;
  1746. }
  1747.  
  1748. /*
  1749. **  Entry function from creating a Cookie: request header
  1750. **  if needed. - AK & FM
  1751. */
  1752. PUBLIC char * LYCookie ARGS4(
  1753.     CONST char *,    hostname,
  1754.     CONST char *,    path,
  1755.     int,        port,
  1756.     BOOL,        secure)
  1757. {
  1758.     char *header = NULL;
  1759.     HTList *hl = domain_list, *next = NULL;
  1760.     domain_entry *de;
  1761.  
  1762.     CTRACE(tfp, "LYCookie: Searching for '%s:%d', '%s'.\n",
  1763.         (hostname ? hostname : "(null)"),
  1764.         port,
  1765.         (path ? path : "(null)"));
  1766.  
  1767.     /*
  1768.      *    Search the cookie_list elements in the domain_list
  1769.      *    for any cookies associated with the //hostname:port/path
  1770.      */
  1771.     while (hl) {
  1772.     de = (domain_entry *)hl->object;
  1773.     next = hl->next;
  1774.  
  1775.     if (de != NULL) {
  1776.         if (!HTList_isEmpty(de->cookie_list)) {
  1777.         /*
  1778.          *  Scan the domain's cookie_list for
  1779.          *  any cookies we should include in
  1780.          *  our request header.
  1781.          */
  1782.         header = scan_cookie_sublist(hostname, path, port,
  1783.                          de->cookie_list, header, secure);
  1784.         } else if (de->bv == QUERY_USER) {
  1785.         /*
  1786.          *  No cookies in this domain, and no default
  1787.          *  accept/reject choice was set by the user,
  1788.          *  so delete the domain. - FM
  1789.          */
  1790.         FREE(de->domain);
  1791.         HTList_delete(de->cookie_list);
  1792.         de->cookie_list = NULL;
  1793.         HTList_removeObject(domain_list, de);
  1794.         de = NULL;
  1795.         }
  1796.     }
  1797.     hl = next;
  1798.     }
  1799.     if (header)
  1800.     return(header);
  1801.  
  1802.     /*
  1803.      *    If we didn't set a header, perhaps all the cookies have
  1804.      *    expired and we deleted the last of them above, so check
  1805.      *    if we should delete and NULL the domain_list. - FM
  1806.      */
  1807.     if (domain_list) {
  1808.     if (HTList_isEmpty(domain_list)) {
  1809.         HTList_delete(domain_list);
  1810.         domain_list = NULL;
  1811.     }
  1812.     }
  1813.     return(NULL);
  1814. }
  1815.  
  1816. /*    LYHandleCookies - F.Macrides (macrides@sci.wfeb.edu)
  1817. **    ---------------
  1818. **
  1819. **  Lists all cookies by domain, and allows deletions of
  1820. **  individual cookies or entire domains, and changes of
  1821. **  'allow' settings.  The list is invoked via the COOKIE_JAR
  1822. **  command (Ctrl-K), and deletions or changes of 'allow'
  1823. **  settings are done by activating links in that list.
  1824. **  The procedure uses a LYNXCOOKIE: internal URL scheme.
  1825. **
  1826. **  Semantics:
  1827. **    LYNXCOOKIE:/            Create and load the Cookie Jar Page.
  1828. **    LYNXCOOKIE://domain        Manipulate the domain.
  1829. **    LYNXCOOKIE://domain/lynxID    Delete cookie with lynxID in domain.
  1830. **
  1831. **    New functions can be added as extensions to the path, and/or by
  1832. **    assigning meanings to ;parameters, a ?searchpart, and/or #fragments.
  1833. */
  1834. PRIVATE int LYHandleCookies ARGS4 (
  1835.     CONST char *,        arg,
  1836.     HTParentAnchor *,    anAnchor,
  1837.     HTFormat,        format_out,
  1838.     HTStream*,        sink)
  1839. {
  1840.     HTFormat format_in = WWW_HTML;
  1841.     HTStream *target = NULL;
  1842.     char buf[1024];
  1843.     char *domain = NULL;
  1844.     char *lynxID = NULL;
  1845.     HTList *dl, *cl, *next;
  1846.     domain_entry *de;
  1847.     cookie *co;
  1848.     char *name = NULL, *value = NULL, *path = NULL;
  1849.     char *comment = NULL, *Address = NULL, *Title = NULL;
  1850.     int ch;
  1851. #ifdef VMS
  1852.     extern BOOLEAN HadVMSInterrupt;
  1853. #endif /* VMS */
  1854.  
  1855.     /*
  1856.      *    Check whether we have something to do. - FM
  1857.      */
  1858.     if (domain_list == NULL) {
  1859.     HTProgress(COOKIE_JAR_IS_EMPTY);
  1860.     sleep(MessageSecs);
  1861.     return(HT_NO_DATA);
  1862.     }
  1863.  
  1864.     /*
  1865.      *    If there's a domain string in the "host" field of the
  1866.      *    LYNXCOOKIE: URL, this is a request to delete something
  1867.      *    or change and 'allow' setting. - FM
  1868.      */
  1869.     if ((domain = HTParse(arg, "", PARSE_HOST)) != NULL) {
  1870.     if (*domain == '\0') {
  1871.         FREE(domain);
  1872.     } else {
  1873.         /*
  1874.          *    If there is a path string (not just a slash) in the
  1875.          *    LYNXCOOKIE: URL, that's a cookie's lynxID and this
  1876.          *    is a request to delete it from the Cookie Jar. - FM
  1877.          */
  1878.         if ((lynxID = HTParse(arg, "", PARSE_PATH)) != NULL) {
  1879.         if (*lynxID == '\0') {
  1880.             FREE(lynxID);
  1881.         }
  1882.         }
  1883.     }
  1884.     }
  1885.     if (domain) {
  1886.     /*
  1887.      *  Seek the domain in the domain_list structure. - FM
  1888.      */
  1889.     for (dl = domain_list; dl != NULL; dl = dl->next) {
  1890.         de = dl->object;
  1891.         if (!(de && de->domain))
  1892.         /*
  1893.          *  First object in the list always is empty. - FM
  1894.          */
  1895.         continue;
  1896.         if (!strcmp(domain, de->domain)) {
  1897.         /*
  1898.          *  We found the domain.  Check
  1899.          *  whether a lynxID is present. - FM
  1900.          */
  1901.         if (lynxID) {
  1902.             /*
  1903.              *    Seek and delete the cookie with this lynxID
  1904.              *    in the domain's cookie list. - FM
  1905.              */
  1906.             for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
  1907.             if ((co = (cookie *)cl->object) == NULL)
  1908.                 /*
  1909.                  *    First object is always empty. - FM
  1910.                  */
  1911.                 continue;
  1912.             if (!strcmp(lynxID, co->lynxID)) {
  1913.                 /*
  1914.                  *    We found the cookie.
  1915.                  *    Delete it if confirmed. - FM
  1916.                  */
  1917.                 if (HTConfirm(DELETE_COOKIE_CONFIRMATION) == FALSE)
  1918.                 return(HT_NO_DATA);
  1919.                 HTList_removeObject(de->cookie_list, co);
  1920.                 freeCookie(co);
  1921.                 co = NULL;
  1922.                 total_cookies--;
  1923.                 if ((de->bv == QUERY_USER &&
  1924.                  HTList_isEmpty(de->cookie_list)) &&
  1925.                 HTConfirm(DELETE_EMPTY_DOMAIN_CONFIRMATION)) {
  1926.                 /*
  1927.                  *  No more cookies in this domain, no
  1928.                  *  default accept/reject choice was set
  1929.                  *  by the user, and got confirmation on
  1930.                  *  deleting the domain, so do it. - FM
  1931.                  */
  1932.                 FREE(de->domain);
  1933.                 HTList_delete(de->cookie_list);
  1934.                 de->cookie_list = NULL;
  1935.                 HTList_removeObject(domain_list, de);
  1936.                 de = NULL;
  1937.                 HTProgress(DOMAIN_EATEN);
  1938.                 } else {
  1939.                 HTProgress(COOKIE_EATEN);
  1940.                 }
  1941.                 sleep(MessageSecs);
  1942.                 break;
  1943.             }
  1944.             }
  1945.         } else {
  1946.             /*
  1947.              *    Prompt whether to delete all of the cookies in
  1948.              *    this domain, or the domain if no cookies in it,
  1949.              *    or to change its 'allow' setting, or to cancel,
  1950.              *    and then act on the user's response. - FM
  1951.              */
  1952.             if (HTList_isEmpty(de->cookie_list)) {
  1953.             _statusline(DELETE_DOMAIN_SET_ALLOW_OR_CANCEL);
  1954.             } else {
  1955.             _statusline(DELETE_COOKIES_SET_ALLOW_OR_CANCEL);
  1956.             }
  1957.             while (1) {
  1958.             ch = LYgetch();
  1959. #ifdef VMS
  1960.             if (HadVMSInterrupt) {
  1961.                 HadVMSInterrupt = FALSE;
  1962.                 ch = 'C';
  1963.             }
  1964. #endif /* VMS */
  1965.             switch(TOUPPER(ch)) {
  1966.                 case 'A':
  1967.                 /*
  1968.                  *  Set to accept all cookies
  1969.                  *  from this domain. - FM
  1970.                  */
  1971.                 de->bv = QUERY_USER;
  1972.                 _user_message(ALWAYS_ALLOWING_COOKIES,
  1973.                           de->domain);
  1974.                 sleep(MessageSecs);
  1975.                 return(HT_NO_DATA);
  1976.  
  1977.                 case 'C':
  1978.                 case 7:    /* Ctrl-G */
  1979.                 case 3:    /* Ctrl-C */
  1980.                 /*
  1981.                  *  Cancelled. - FM
  1982.                  */
  1983.                 _statusline(CANCELLED);
  1984.                 sleep(MessageSecs);
  1985.                 return(HT_NO_DATA);
  1986.  
  1987.                 case 'D':
  1988.                 if (HTList_isEmpty(de->cookie_list)) {
  1989.                     /*
  1990.                      *    We had an empty domain, so we
  1991.                      *    were asked to delete it. - FM
  1992.                      */
  1993.                     FREE(de->domain);
  1994.                     HTList_delete(de->cookie_list);
  1995.                     de->cookie_list = NULL;
  1996.                     HTList_removeObject(domain_list, de);
  1997.                     de = NULL;
  1998.                     HTProgress(DOMAIN_EATEN);
  1999.                     sleep(MessageSecs);
  2000.                     break;
  2001.                 }
  2002. Delete_all_cookies_in_domain:
  2003.                 /*
  2004.                  *  Delete all cookies in this domain. - FM
  2005.                  */
  2006.                 cl = de->cookie_list;
  2007.                 while (cl) {
  2008.                     next = cl->next;
  2009.                     co = cl->object;
  2010.                     if (co) {
  2011.                     HTList_removeObject(de->cookie_list,
  2012.                                 co);
  2013.                     freeCookie(co);
  2014.                     co = NULL;
  2015.                     total_cookies--;
  2016.                     }
  2017.                     cl = next;
  2018.                 }
  2019.                 HTProgress(DOMAIN_COOKIES_EATEN);
  2020.                 sleep(MessageSecs);
  2021.                 /*
  2022.                  *  If a default accept/reject
  2023.                  *  choice is set, we're done. - FM
  2024.                  */
  2025.                 if (de->bv != QUERY_USER)
  2026.                     return(HT_NO_DATA);
  2027.                 /*
  2028.                  *  Check whether to delete
  2029.                  *  the empty domain. - FM
  2030.                  */
  2031.                 if(HTConfirm(
  2032.                     DELETE_EMPTY_DOMAIN_CONFIRMATION)) {
  2033.                     FREE(de->domain);
  2034.                     HTList_delete(de->cookie_list);
  2035.                     de->cookie_list = NULL;
  2036.                     HTList_removeObject(domain_list, de);
  2037.                     de = NULL;
  2038.                     HTProgress(DOMAIN_EATEN);
  2039.                     sleep(MessageSecs);
  2040.                 }
  2041.                 break;
  2042.  
  2043.                 case 'P':
  2044.                 /*
  2045.                  *  Set to prompt for cookie acceptence
  2046.                  *  from this domain. - FM
  2047.                  */
  2048.                 de->bv = QUERY_USER;
  2049.                 _user_message(PROMTING_TO_ALLOW_COOKIES,
  2050.                           de->domain);
  2051.                 sleep(MessageSecs);
  2052.                 return(HT_NO_DATA);
  2053.  
  2054.                 case 'V':
  2055.                 /*
  2056.                  *  Set to reject all cookies
  2057.                  *  from this domain. - FM
  2058.                  */
  2059.                 de->bv = REJECT_ALWAYS;
  2060.                 _user_message(NEVER_ALLOWING_COOKIES,
  2061.                           de->domain);
  2062.                 sleep(MessageSecs);
  2063.                 if ((!HTList_isEmpty(de->cookie_list)) &&
  2064.                     HTConfirm(DELETE_ALL_COOKIES_IN_DOMAIN))
  2065.                     goto Delete_all_cookies_in_domain;
  2066.                 return(HT_NO_DATA);
  2067.  
  2068.                 default:
  2069.                 continue;
  2070.             }
  2071.             break;
  2072.             }
  2073.         }
  2074.         break;
  2075.         }
  2076.     }
  2077.     if (HTList_isEmpty(domain_list)) {
  2078.         /*
  2079.          *    There are no more domains left,
  2080.          *    so delete the domain_list. - FM
  2081.          */
  2082.         HTList_delete(domain_list);
  2083.         domain_list = NULL;
  2084.         HTProgress(ALL_COOKIES_EATEN);
  2085.         sleep(MessageSecs);
  2086.     }
  2087.     return(HT_NO_DATA);
  2088.     }
  2089.  
  2090.     /*
  2091.      *    If we get to here, it was a LYNXCOOKIE:/ URL
  2092.      *    for creating and displaying the Cookie Jar Page,
  2093.      *    or we didn't find the domain or cookie in a
  2094.      *    deletion request.  Set up an HTML stream and
  2095.      *    return an updated Cookie Jar Page. - FM
  2096.      */
  2097.     target = HTStreamStack(format_in,
  2098.                format_out,
  2099.                sink, anAnchor);
  2100.     if (!target || target == NULL) {
  2101.     sprintf(buf, CANNOT_CONVERT_I_TO_O,
  2102.         HTAtom_name(format_in), HTAtom_name(format_out));
  2103.     HTAlert(buf);
  2104.     return(HT_NOT_LOADED);
  2105.     }
  2106.  
  2107.     /*
  2108.      *    Load HTML strings into buf and pass buf
  2109.      *    to the target for parsing and rendering. - FM
  2110.      */
  2111.     sprintf(buf, "<HEAD>\n<TITLE>%s</title>\n</HEAD>\n<BODY>\n",
  2112.          COOKIE_JAR_TITLE);
  2113.     (*target->isa->put_block)(target, buf, strlen(buf));
  2114.  
  2115.     sprintf(buf, "<H1>%s</H1>\n", REACHED_COOKIE_JAR_PAGE);
  2116.     (*target->isa->put_block)(target, buf, strlen(buf));
  2117.     sprintf(buf, "<H2>%s Version %s</H2>\n", LYNX_NAME, LYNX_VERSION);
  2118.     (*target->isa->put_block)(target, buf, strlen(buf));
  2119.  
  2120.     sprintf(buf, "<NOTE>%s\n", ACTIVATE_TO_GOBBLE);
  2121.     (*target->isa->put_block)(target, buf, strlen(buf));
  2122.     sprintf(buf, "%s</NOTE>\n", OR_CHANGE_ALLOW);
  2123.     (*target->isa->put_block)(target, buf, strlen(buf));
  2124.  
  2125.     sprintf(buf, "<DL COMPACT>\n");
  2126.     (*target->isa->put_block)(target, buf, strlen(buf));
  2127.     for (dl = domain_list; dl != NULL; dl = dl->next) {
  2128.     de = dl->object;
  2129.     if (de == NULL)
  2130.         /*
  2131.          *    First object always is NULL. - FM
  2132.          */
  2133.         continue;
  2134.  
  2135.     /*
  2136.      *  Show the domain link and 'allow' setting. - FM
  2137.      */
  2138.     sprintf(buf, "<DT><A HREF=\"LYNXCOOKIE://%s/\">Domain=%s</A>\n",
  2139.               de->domain, de->domain);
  2140.     (*target->isa->put_block)(target, buf, strlen(buf));
  2141.     switch (de->bv) {
  2142.         case (ACCEPT_ALWAYS):
  2143.         sprintf(buf, COOKIES_ALWAYS_ALLOWED);
  2144.         break;
  2145.         case (REJECT_ALWAYS):
  2146.         sprintf(buf, COOKIES_NEVER_ALLOWED);
  2147.         break;
  2148.         case (QUERY_USER):
  2149.         sprintf(buf, COOKIES_ALLOWED_VIA_PROMPT);
  2150.         break;
  2151.     }
  2152.     (*target->isa->put_block)(target, buf, strlen(buf));
  2153.  
  2154.     /*
  2155.      *  Show the domain's cookies. - FM
  2156.      */
  2157.     for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
  2158.         if ((co = (cookie *)cl->object) == NULL)
  2159.         /*
  2160.          *  First object is always NULL. - FM
  2161.          */
  2162.         continue;
  2163.  
  2164.         /*
  2165.          *    Show the name=value pair. - FM
  2166.          */
  2167.         if (co->name) {
  2168.         StrAllocCopy(name, co->name);
  2169.         LYEntify(&name, TRUE);
  2170.         } else {
  2171.         StrAllocCopy(name, NO_NAME);
  2172.         }
  2173.         if (co->value) {
  2174.         StrAllocCopy(value, co->value);
  2175.         LYEntify(&value, TRUE);
  2176.         } else {
  2177.         StrAllocCopy(value, NO_VALUE);
  2178.         }
  2179.         sprintf(buf, "<DD><A HREF=\"LYNXCOOKIE://%s/%s\">%s=%s</A>\n",
  2180.              de->domain, co->lynxID, name, value);
  2181.         FREE(name);
  2182.         FREE(value);
  2183.         (*target->isa->put_block)(target, buf, strlen(buf));
  2184.  
  2185.         /*
  2186.          *    Show the path, port, secure and discard setting. - FM
  2187.          */
  2188.         if (co->path) {
  2189.         StrAllocCopy(path, co->path);
  2190.         LYEntify(&path, TRUE);
  2191.         } else {
  2192.         StrAllocCopy(path, "/");
  2193.         }
  2194.         sprintf(buf, "<DD>Path=%s\n<DD>Port: %d Secure: %s Discard: %s\n",
  2195.              path, co->port,
  2196.              ((co->flags & COOKIE_FLAG_SECURE) ? "YES" : "NO"),
  2197.              ((co->flags & COOKIE_FLAG_DISCARD) ? "YES" : "NO"));
  2198.         FREE(path);
  2199.         (*target->isa->put_block)(target, buf, strlen(buf));
  2200.  
  2201.         /*
  2202.          *    Show the list of acceptable ports, if present. - FM
  2203.          */
  2204.         if (co->PortList) {
  2205.         sprintf(buf, "<DD>PortList=\"%s\"\n", co->PortList);
  2206.         (*target->isa->put_block)(target, buf, strlen(buf));
  2207.         }
  2208.  
  2209.         /*
  2210.          *    Show the commentURL, if we have one. - FM
  2211.          */
  2212.         if (co->commentURL) {
  2213.         StrAllocCopy(Address, co->commentURL);
  2214.         LYEntify(&Address, FALSE);
  2215.         StrAllocCopy(Title, co->commentURL);
  2216.         LYEntify(&Title, TRUE);
  2217.         sprintf(buf,
  2218.             "<DD>CommentURL: <A href=\"%s\">%s</A>\n",
  2219.             Address,
  2220.             Title);
  2221.         FREE(Address);
  2222.         FREE(Title);
  2223.         (*target->isa->put_block)(target, buf, strlen(buf));
  2224.         }
  2225.  
  2226.         /*
  2227.          *    Show the comment, if we have one. - FM
  2228.          */
  2229.         if (co->comment) {
  2230.         StrAllocCopy(comment, co->comment);
  2231.         LYEntify(&comment, TRUE);
  2232.         sprintf(buf, "<DD>Comment: %s\n", comment);
  2233.         FREE(comment);
  2234.         (*target->isa->put_block)(target, buf, strlen(buf));
  2235.         }
  2236.  
  2237.         /*
  2238.          *    Show the Maximum Gobble Date. - FM
  2239.          */
  2240.         sprintf(buf, "<DD><EM>Maximum Gobble Date:</EM> %s%s",
  2241.              ((co->expires > 0 &&
  2242.                !(co->flags & COOKIE_FLAG_DISCARD))
  2243.                         ?
  2244.             ctime(&co->expires) : END_OF_SESSION),
  2245.              ((co->expires > 0 &&
  2246.                !(co->flags & COOKIE_FLAG_DISCARD))
  2247.                         ?
  2248.                      "" : "\n"));
  2249.         (*target->isa->put_block)(target, buf, strlen(buf));
  2250.     }
  2251.     sprintf(buf, "</DT>\n");
  2252.     (*target->isa->put_block)(target, buf, strlen(buf));
  2253.     }
  2254.     sprintf(buf, "</DL>\n</BODY>\n");
  2255.     (*target->isa->put_block)(target, buf, strlen(buf));
  2256.  
  2257.     /*
  2258.      *    Free the target to complete loading of the
  2259.      *    Cookie Jar Page, and report a successful load. - FM
  2260.      */
  2261.     (*target->isa->_free)(target);
  2262.     return(HT_LOADED);
  2263. }
  2264.  
  2265. #ifdef GLOBALDEF_IS_MACRO
  2266. #define _LYCOOKIE_C_GLOBALDEF_1_INIT { "LYNXCOOKIE",LYHandleCookies,0}
  2267. GLOBALDEF (HTProtocol,LYLynxCookies,_LYCOOKIE_C_GLOBALDEF_1_INIT);
  2268. #else
  2269. GLOBALDEF PUBLIC HTProtocol LYLynxCookies = {"LYNXCOOKIE",LYHandleCookies,0};
  2270. #endif /* GLOBALDEF_IS_MACRO */
  2271.