"Fossies" - the Fresh Open Source Software Archive

Member "lynx2.9.0dev.1/src/LYCookie.c" (26 Jan 2019, 80799 Bytes) of package /linux/www/lynx2.9.0dev.1.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "LYCookie.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.9_vs_2.9.0dev.1.

    1 /*
    2  * $LynxId: LYCookie.c,v 1.147 2019/01/26 00:50:13 tom Exp $
    3  *
    4  *                 Lynx Cookie Support         LYCookie.c
    5  *                 ===================
    6  *
    7  *  Author: AMK A.M. Kuchling (amk@magnet.com)  12/25/96
    8  *
    9  *  Incorporated with mods by FM            01/16/97
   10  *
   11  *  Based on:
   12  *  http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-mgmt-05.txt
   13  *
   14  *  Updated for:
   15  *   http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
   16  *      - FM                    1997-07-09
   17  *
   18  *  Updated for:
   19  *   ftp://ds.internic.net/internet-drafts/draft-ietf-http-state-man-mec-03.txt
   20  *      - FM                    1997-08-02
   21  *
   22  *  Partially checked against:
   23  *   http://www.ietf.org/internet-drafts/draft-ietf-http-state-man-mec-10.txt
   24  *      - kw                    1998-12-11
   25  *
   26  *  Modified to follow RFC-6265 regarding leading dot of Domain, and
   27  *  matching of hostname vs domain (2011/06/10 -TD)
   28  *
   29  *  Modified to address differences between RFC-6262 versus RFC-2109 and
   30  *  RFC-2965 by making the older behavior optional (2019/01/25 -TD)
   31  *
   32  *  FM's TO DO: (roughly in order of decreasing priority)
   33       * Persistent cookies are still experimental.  Presently cookies
   34     lose many pieces of information that distinguish
   35     version 1 from version 0 cookies.  There is no easy way around
   36     that with the current cookie file format.  Ports are currently
   37     not stored persistently at all which is clearly wrong.
   38       * We currently don't do anything special for unverifiable
   39     transactions to third-party hosts.
   40       * We currently don't use effective host names or check for
   41     Domain=.local.
   42       * Hex escaping isn't considered at all.  Any semi-colons, commas,
   43     or spaces actually in cookie names or values (i.e., not serving
   44     as punctuation for the overall Set-Cookie value) should be hex
   45     escaped if not quoted, but presumably the server is expecting
   46     them to be hex escaped in our Cookie request header as well, so
   47     in theory we need not unescape them.  We'll see how this works
   48     out in practice.
   49       * The prompt should show more information about the cookie being
   50     set in Novice mode.
   51       * The truncation heuristic in HTConfirmCookie should probably be
   52     smarter, smart enough to leave a really short name/value string
   53     alone.
   54       * We protect against denial-of-service attacks (see section 6.3.1 of the
   55     draft) by limiting the number of cookies from a domain, limiting the
   56     total number of cookies, and limiting the number of bytes from a
   57     processed cookie, but we count on the normal garbage collections to
   58     bring us back down under the limits, rather than actively removing
   59     cookies and/or domains based on age or frequency of use.
   60       * If a cookie has the secure flag set, we presently treat only SSL
   61     connections as secure.  This may need to be expanded for other
   62     secure communication protocols that become standardized.
   63 */
   64 
   65 #include <HTUtils.h>
   66 #include <HTAccess.h>
   67 #include <HTParse.h>
   68 #include <HTAlert.h>
   69 #include <LYCurses.h>
   70 #include <LYUtils.h>
   71 #include <LYCharUtils.h>
   72 #include <LYClean.h>
   73 #include <LYGlobalDefs.h>
   74 #include <LYEdit.h>
   75 #include <LYStrings.h>
   76 #include <GridText.h>
   77 #include <LYCookie.h>
   78 
   79 #include <LYLeaks.h>
   80 
   81 /* default for new domains, one of the invcheck_behaviour_t values: */
   82 #define DEFAULT_INVCHECK_BV INVCHECK_QUERY
   83 
   84 #define CTrace(p) CTRACE2(TRACE_COOKIES, p)
   85 
   86 #define LeadingDot(s)     ((s)[0] == '.')
   87 #define SkipLeadingDot(s) (LeadingDot(s) ? ((s) + 1) : (s))
   88 
   89 #define AssumeCookieVersion(p) \
   90     if (USE_RFC_2965 && (p)->version < 1) { \
   91         (p)->version = 1; \
   92     }
   93 
   94 /*
   95  *  The first level of the cookie list is a list indexed by the domain
   96  *  string; cookies with the same domain will be placed in the same
   97  *  list.  Thus, finding the cookies that apply to a given URL is a
   98  *  two-level scan; first we check each domain to see if it applies,
   99  *  and if so, then we check the paths of all the cookies on that
  100  *  list.  We keep a running total of cookies as we add or delete
  101  *  them
  102  */
  103 static HTList *domain_list = NULL;
  104 static HTList *cookie_list = NULL;
  105 static int total_cookies = 0;
  106 
  107 struct _cookie {
  108     char *lynxID;       /* Lynx cookie identifier */
  109     char *name;         /* Name of this cookie */
  110     char *value;        /* Value of this cookie */
  111     int version;        /* Cookie protocol version (=1) */
  112     char *comment;      /* Comment to show to user */
  113     char *commentURL;       /* URL for comment to show to user */
  114     char *domain;       /* Domain for which this cookie is valid */
  115     char *ddomain;      /* Domain without leading "." */
  116     int port;           /* Server port from which this cookie was given (usu. 80) */
  117     char *PortList;     /* List of ports for which cookie can be sent */
  118     char *path;         /* Path prefix for which this cookie is valid */
  119     int pathlen;        /* Length of the path */
  120     int flags;          /* Various flags */
  121     time_t expires;     /* The time when this cookie expires */
  122     BOOL quoted;        /* Was a value quoted in the Set-Cookie header? */
  123 };
  124 typedef struct _cookie cookie;
  125 
  126 #define COOKIE_FLAG_SECURE      1   /* If set, cookie requires secure links */
  127 #define COOKIE_FLAG_DISCARD     2   /* If set, expire at end of session */
  128 #define COOKIE_FLAG_EXPIRES_SET 4   /* If set, an expiry date was set */
  129 #define COOKIE_FLAG_DOMAIN_SET  8   /* If set, an non-default domain was set */
  130 #define COOKIE_FLAG_PATH_SET   16   /* If set, an non-default path was set */
  131 #define COOKIE_FLAG_FROM_FILE  32   /* If set, this cookie was persistent */
  132 
  133 static void MemAllocCopy(char **dest,
  134              const char *start,
  135              const char *end)
  136 {
  137     char *temp;
  138 
  139     if (!(start && end) || (end <= start)) {
  140     HTSACopy(dest, "");
  141     return;
  142     }
  143 
  144     temp = typecallocn(char, (unsigned)(end - start) + 1);
  145     if (temp == NULL)
  146     outofmem(__FILE__, "MemAllocCopy");
  147     LYStrNCpy(temp, start, (end - start));
  148     HTSACopy(dest, temp);
  149     FREE(temp);
  150 }
  151 
  152 static cookie *newCookie(void)
  153 {
  154     cookie *p = typecalloc(cookie);
  155 
  156     if (p == NULL)
  157     outofmem(__FILE__, "newCookie");
  158 
  159     HTSprintf0(&(p->lynxID), "%p", (void *) p);
  160     p->port = 80;
  161     return p;
  162 }
  163 
  164 static void freeCookie(cookie * co)
  165 {
  166     if (co) {
  167     FREE(co->lynxID);
  168     FREE(co->name);
  169     FREE(co->value);
  170     FREE(co->comment);
  171     FREE(co->commentURL);
  172     FREE(co->domain);
  173     FREE(co->ddomain);
  174     FREE(co->path);
  175     FREE(co->PortList);
  176     FREE(co);
  177     }
  178 }
  179 
  180 static void freeCookies(domain_entry * de)
  181 {
  182     FREE(de->domain);
  183     FREE(de->ddomain);
  184     HTList_delete(de->cookie_list);
  185     de->cookie_list = NULL;
  186 }
  187 
  188 #ifdef LY_FIND_LEAKS
  189 static void LYCookieJar_free(void)
  190 {
  191     HTList *dl = domain_list;
  192     domain_entry *de = NULL;
  193     HTList *cl = NULL, *next = NULL;
  194     cookie *co = NULL;
  195 
  196     CTrace((tfp, "LYCookieJar_free\n"));
  197     while (dl) {
  198     if ((de = dl->object) != NULL) {
  199         CTrace((tfp, "...LYCookieJar_free domain %s\n", NonNull(de->ddomain)));
  200         cl = de->cookie_list;
  201         while (cl) {
  202         next = cl->next;
  203         co = cl->object;
  204         if (co) {
  205             HTList_removeObject(de->cookie_list, co);
  206             freeCookie(co);
  207         }
  208         cl = next;
  209         }
  210         freeCookies(de);
  211         FREE(dl->object);
  212     }
  213     dl = dl->next;
  214     }
  215     cookie_list = NULL;
  216     HTList_delete(domain_list);
  217     domain_list = NULL;
  218 }
  219 #endif /* LY_FIND_LEAKS */
  220 
  221 /*
  222  * RFC 2109 -
  223  * 4.2.2  Set-Cookie Syntax
  224  * An explicitly specified domain must always start with a dot.
  225  * 4.3.2  Rejecting Cookies
  226  * ...rejects a cookie (shall not store its information) if any of the
  227  * following is true:
  228  * ...
  229  * The value for the Domain attribute contains no embedded dots or does not
  230  * start with a dot.
  231  *
  232  * RFC 2965 -
  233  * 3.2.2  Set-Cookie2 Syntax
  234  * Domain=value
  235  *    OPTIONAL.  The value of the Domain attribute specifies the domain
  236  *    for which the cookie is valid.  If an explicitly specified value
  237  *    does not start with a dot, the user agent supplies a leading dot.
  238  */
  239 static BOOLEAN has_embedded_dot(const char *value)
  240 {
  241     BOOLEAN leading = NO;
  242     BOOLEAN embedded = NO;
  243     const char *p;
  244 
  245     for (p = value; *p != '\0'; ++p) {
  246     if (*p == '.') {
  247         if (p == value) {
  248         leading = YES;
  249         } else if (p[1] != '\0') {
  250         embedded = YES;
  251         } else {
  252         embedded = NO;
  253         }
  254     }
  255     }
  256     return (leading || USE_RFC_2965) && embedded;
  257 }
  258 
  259 /*
  260  * RFC 6265 -
  261  * 4.1.2.3.  The Domain Attribute
  262  * (Note that a leading %x2E ("."), if present, is ignored even though that
  263  * character is not permitted, but a trailing %x2E ("."), if present, will
  264  * cause the user agent to ignore the attribute.)
  265  */
  266 static BOOLEAN has_trailing_dot(const char *value)
  267 {
  268     BOOLEAN result = NO;
  269     const char *p;
  270 
  271     for (p = value; *p != '\0'; ++p) {
  272     if (*p == '.') {
  273         if (p[1] == '\0') {
  274         result = YES;
  275         break;
  276         }
  277     }
  278     }
  279     return result;
  280 }
  281 
  282 /*
  283  * Compare a string against a domain as specified in RFC-6265 Section 5.1.3
  284  */
  285 static BOOLEAN domain_matches(const char *value,
  286                   const char *domain)
  287 {
  288     BOOLEAN result = NO;
  289 
  290     if (isEmpty(value)) {
  291     CTrace((tfp, "BUG: comparing empty value in domain_matches\n"));
  292     } else if (isEmpty(domain)) {
  293     CTrace((tfp, "BUG: comparing empty domain in domain_matches\n"));
  294     } else {
  295     if (!strcasecomp(value, domain)) {
  296         result = YES;
  297     } else {
  298         int value_len = (int) strlen(value);
  299         int suffix_len = (int) strlen(domain);
  300         int offset = value_len - suffix_len;
  301 
  302         if (offset > 1
  303         && value[offset - 1] == '.'
  304         && !strcasecomp(value + offset, domain)) {
  305         result = YES;
  306         }
  307     }
  308     }
  309     return result;
  310 }
  311 
  312 /*
  313  *  Compare the current port with a port list as specified in Section 4.3 of:
  314  *   http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
  315  *  - FM
  316  */
  317 static BOOLEAN port_matches(int port,
  318                 const char *list)
  319 {
  320     const char *number = list;
  321 
  322     if (!(number && isdigit(UCH(*number))))
  323     return (FALSE);
  324 
  325     while (*number != '\0') {
  326     if (atoi(number) == port) {
  327         return (TRUE);
  328     }
  329     while (isdigit(UCH(*number))) {
  330         number++;
  331     }
  332     while (*number != '\0' && !isdigit(UCH(*number))) {
  333         number++;
  334     }
  335     }
  336 
  337     return (FALSE);
  338 }
  339 
  340 /*
  341  * Returns the length of the given path ignoring trailing slashes.
  342  */
  343 static int ignore_trailing_slash(const char *a)
  344 {
  345     int len = (int) strlen(a);
  346 
  347     while (len > 1 && a[len - 1] == '/')
  348     --len;
  349     return len;
  350 }
  351 
  352 /*
  353  * Check if the path 'a' is a prefix of path 'b', ignoring trailing slashes
  354  * in either, since they denote an empty component.
  355  */
  356 static BOOL is_prefix(const char *a, const char *b)
  357 {
  358     int len_a = ignore_trailing_slash(a);
  359     int len_b = ignore_trailing_slash(b);
  360 
  361     if (len_a > len_b) {
  362     return FALSE;
  363     } else {
  364     if (StrNCmp(a, b, (unsigned) len_a) != 0) {
  365         return FALSE;
  366     }
  367     if (len_a < len_b && (len_a > 1 || a[0] != '/')) {
  368         if (b[len_a] != '\0'
  369         && b[len_a] != '/') {
  370         return FALSE;
  371         }
  372     }
  373     }
  374     return TRUE;
  375 }
  376 
  377 /*
  378  * Find the domain-entry for the given name.
  379  */
  380 static domain_entry *find_domain_entry(const char *name)
  381 {
  382     HTList *hl;
  383     domain_entry *de = NULL;
  384     const char *find;
  385 
  386     if (name != 0
  387     && *(find = SkipLeadingDot(name)) != '\0') {
  388     for (hl = domain_list; hl != NULL; hl = hl->next) {
  389         de = (domain_entry *) hl->object;
  390         if (de != NULL && de->domain != NULL && de->ddomain != NULL) {
  391         CTrace((tfp,
  392             "...test_domain_entry(%s) ->(%s) bv:%u, invcheck_bv:%u\n",
  393             find,
  394             NonNull(de->ddomain),
  395             de->bv,
  396             de->invcheck_bv));
  397         if (!strcasecomp(find, de->ddomain)) {
  398             break;
  399         }
  400         }
  401         de = NULL;
  402     }
  403     }
  404     CTrace((tfp, "find_domain_entry(%s) bv:%d, invcheck_bv:%d\n",
  405         name,
  406         de ? (int) de->bv : -1,
  407         de ? (int) de->invcheck_bv : -1));
  408     return de;
  409 }
  410 
  411 static void SetCookieDomain(cookie * co, const char *domain)
  412 {
  413     StrAllocCopy(co->ddomain, SkipLeadingDot(domain));
  414     CTrace((tfp, "SetCookieDomain(%s)\n", co->ddomain));
  415 }
  416 
  417 /*
  418  *  Store a cookie somewhere in the domain list. - AK & FM
  419  */
  420 static void store_cookie(cookie * co, const char *hostname,
  421              const char *path)
  422 {
  423     HTList *hl, *next;
  424     cookie *c2;
  425     time_t now = time(NULL);
  426     int pos;
  427     const char *ptr;
  428     domain_entry *de = NULL;
  429     BOOL Replacement = FALSE;
  430     int invprompt_reasons = 0;  /* what is wrong with this cookie - kw */
  431 
  432 #define FAILS_COND1 0x01
  433 #define FAILS_COND4 0x02
  434 
  435     if (co == NULL)
  436     return;
  437 
  438     /*
  439      * Ensure that the domain list exists.
  440      */
  441     if (domain_list == NULL) {
  442 #ifdef LY_FIND_LEAKS
  443     atexit(LYCookieJar_free);
  444 #endif
  445     domain_list = HTList_new();
  446     total_cookies = 0;
  447     }
  448 
  449     /*
  450      * Look through domain_list to see if the cookie's domain is already
  451      * listed.
  452      */
  453     cookie_list = NULL;
  454     if ((de = find_domain_entry(co->domain)) != NULL)
  455     cookie_list = de->cookie_list;
  456 
  457     /*
  458      * Apply sanity checks.
  459      *
  460      * RFC 2109 -
  461      * Section 4.3.2, condition 1:  The value for the Path attribute is not a
  462      * prefix of the request-URI.
  463      *
  464      * If cookie checking for this domain is set to INVCHECK_LOOSE, then we
  465      * want to bypass this check.  The user should be queried if set to
  466      * INVCHECK_QUERY.
  467      *
  468      * RFC 6265 -
  469      * Section 4.1.2.4 describes Path, but omits any mention of the user agent
  470      * rejecting a cookie because of Path.  Instead, it deals only with the
  471      * cases where a cookie returned by the user agent would be valid, based on
  472      * Path.  In section 8.6, RFC 6265 presents an example which would not have
  473      * been valid with RFC 2109 to claim that the Path attribute is unreliable
  474      * from the standpoint of the user agent.
  475      *
  476      * RFC 6265 does not go into any detail regarding its differences from the
  477      * older RFCs 2109 / 2965.  The relevant text covering all of these changes
  478      * is just this (no case studies are cited):
  479      * User agents MUST implement the more liberal processing rules defined in
  480      * Section 5, in order to maximize interoperability with existing servers
  481      * that do not conform to the well-behaved profile defined in Section 4.
  482      */
  483     if (!USE_RFC_6265 && !is_prefix(co->path, path)) {
  484     invcheck_behaviour_t invcheck_bv = (de ? de->invcheck_bv
  485                         : DEFAULT_INVCHECK_BV);
  486 
  487     switch (invcheck_bv) {
  488     case INVCHECK_LOOSE:
  489         break;      /* continue as if nothing were wrong */
  490 
  491     case INVCHECK_QUERY:
  492         /* will prompt later if we get that far */
  493         invprompt_reasons |= FAILS_COND1;
  494         break;
  495 
  496     case INVCHECK_STRICT:
  497         CTrace((tfp,
  498             "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
  499             co->path, path));
  500         freeCookie(co);
  501         return;
  502     }
  503     }
  504 
  505     /*
  506      * The next 4 conditions do NOT apply if the domain is still
  507      * the default of request-host. (domains - case insensitive).
  508      */
  509     if (strcasecomp(co->domain, hostname) != 0) {
  510     /*
  511      * The hostname does not contain a dot.
  512      */
  513     if (StrChr(hostname, '.') == NULL) {
  514         CTrace((tfp, "store_cookie: Rejecting because '%s' has no dot.\n",
  515             hostname));
  516         freeCookie(co);
  517         return;
  518     }
  519 
  520     /*
  521      * RFC 2109 -
  522      * Section 4.3.2, condition 2:  The value for the Domain attribute
  523      * contains no embedded dots or does not start with a dot.  (A dot is
  524      * embedded if it's neither the first nor last character.) Note that we
  525      * added a lead dot ourselves if a domain attribute value otherwise
  526      * qualified.  - FM
  527      *
  528      * RFC 6265 -
  529      * If the first character of the attribute-value string is %x2E ("."):
  530      *
  531      * Let cookie-domain be the attribute-value without the leading %x2E
  532      * (".") character.
  533      *
  534      * Otherwise:
  535      *
  536      * Let cookie-domain be the entire attribute-value.
  537      *
  538      * Convert the cookie-domain to lower case.
  539      */
  540     SetCookieDomain(co, co->domain);
  541     if (isEmpty(co->ddomain)) {
  542         CTrace((tfp, "store_cookie: Rejecting domain '%s'.\n", co->ddomain));
  543         freeCookie(co);
  544         return;
  545     }
  546     if (!USE_RFC_6265) {
  547         if (!has_embedded_dot(co->ddomain)) {
  548         CTrace((tfp, "store_cookie: Rejecting domain '%s'.\n", co->ddomain));
  549         freeCookie(co);
  550         return;
  551         }
  552     } else {
  553         if (has_trailing_dot(co->ddomain)) {
  554         CTrace((tfp, "store_cookie: Rejecting domain '%s'.\n", co->ddomain));
  555         freeCookie(co);
  556         return;
  557         }
  558     }
  559 
  560     /*
  561      * RFC 2109 -
  562      * Section 4.3.2, condition 3:  The value for the request-host does not
  563      * domain-match the Domain attribute.
  564      *
  565      * RFC 6265 -
  566      * Section 4.1.2.3,
  567      * The user agent will reject cookies unless the Domain attribute
  568      * specifies a scope for the cookie that would include the origin
  569      * server.
  570      */
  571     if (!domain_matches(hostname, co->ddomain)) {
  572         CTrace((tfp,
  573             "store_cookie: Rejecting domain '%s' for host '%s'.\n",
  574             co->ddomain, hostname));
  575         freeCookie(co);
  576         return;
  577     }
  578 
  579     /*
  580      * RFC 2109 -
  581      * Section 4.3.2, condition 4:  The request-host is an HDN (not IP
  582      * address) and has the form HD, where D is the value of the Domain
  583      * attribute, and H is a string that contains one or more dots.
  584      *
  585      * If cookie checking for this domain is set to INVCHECK_LOOSE, then we
  586      * want to bypass this check.  The user should be queried if set to
  587      * INVCHECK_QUERY.
  588      *
  589      * RFC 6265 -
  590      * There is nothing comparable in RFC 6265, since this check appears to
  591      * have reflected assumptions about how domain names were constructed
  592      * when RFC 2109 was written.  Section 5.1.3.  (Domain Matching) is
  593      * loosely related to these assumptions.
  594      */
  595     if (!USE_RFC_6265) {
  596         ptr = ((hostname + strlen(hostname)) - strlen(co->domain));
  597         if (StrChr(hostname, '.') < ptr) {
  598         invcheck_behaviour_t invcheck_bv = (de ? de->invcheck_bv
  599                             : DEFAULT_INVCHECK_BV);
  600 
  601         switch (invcheck_bv) {
  602         case INVCHECK_LOOSE:
  603             break;  /* continue as if nothing were wrong */
  604 
  605         case INVCHECK_QUERY:
  606             invprompt_reasons |= FAILS_COND4;
  607             break;  /* will prompt later if we get that far */
  608 
  609         case INVCHECK_STRICT:
  610             CTrace((tfp,
  611                 "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
  612                 co->path, path));
  613             freeCookie(co);
  614             return;
  615         }
  616         }
  617     }
  618     }
  619 
  620     /*
  621      * If we found reasons for issuing an invalid cookie confirmation prompt,
  622      * do that now.  Rejection by the user here is the last chance to
  623      * completely ignore this cookie; after it passes this hurdle, it may at
  624      * least supersede a previous cookie (even if it finally gets rejected).  -
  625      * kw
  626      */
  627     if (invprompt_reasons) {
  628     char *msg = 0;
  629 
  630     if (invprompt_reasons & FAILS_COND4) {
  631         HTSprintf0(&msg,
  632                INVALID_COOKIE_DOMAIN_CONFIRMATION,
  633                co->ddomain,
  634                hostname);
  635         if (!HTForcedPrompt(cookie_noprompt, msg, NO)) {
  636         CTrace((tfp,
  637             "store_cookie: Rejecting domain '%s' for host '%s'.\n",
  638             co->ddomain,
  639             hostname));
  640         freeCookie(co);
  641         FREE(msg);
  642         return;
  643         }
  644     }
  645     if (invprompt_reasons & FAILS_COND1) {
  646         HTSprintf0(&msg,
  647                INVALID_COOKIE_PATH_CONFIRMATION,
  648                co->path, path);
  649         if (!HTForcedPrompt(cookie_noprompt, msg, NO)) {
  650         CTrace((tfp,
  651             "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
  652             co->path, path));
  653         freeCookie(co);
  654         FREE(msg);
  655         return;
  656         }
  657     }
  658     FREE(msg);
  659     }
  660 
  661     if (de == NULL) {
  662     /*
  663      * Domain not found; add a new entry for this domain.
  664      */
  665     de = typecalloc(domain_entry);
  666     if (de == NULL)
  667         outofmem(__FILE__, "store_cookie");
  668 
  669     de->bv = QUERY_USER;
  670     de->invcheck_bv = DEFAULT_INVCHECK_BV;  /* should this go here? */
  671     cookie_list = de->cookie_list = HTList_new();
  672     StrAllocCopy(de->domain, co->domain);
  673     StrAllocCopy(de->ddomain, co->ddomain);
  674     HTList_appendObject(domain_list, de);
  675     }
  676 
  677     /*
  678      * Loop over the cookie list, deleting expired and matching cookies.
  679      */
  680     hl = cookie_list;
  681     pos = 0;
  682     while (hl) {
  683     c2 = (cookie *) hl->object;
  684     next = hl->next;
  685     /*
  686      * Check if this cookie has expired.
  687      */
  688     if ((c2 != NULL) &&
  689         (c2->flags & COOKIE_FLAG_EXPIRES_SET) &&
  690         c2->expires <= now) {
  691         HTList_removeObject(cookie_list, c2);
  692         freeCookie(c2);
  693         c2 = NULL;
  694         total_cookies--;
  695 
  696         /*
  697          * Check if this cookie matches the one we're inserting.
  698          */
  699     } else if ((c2) &&
  700            !strcasecomp(co->ddomain, c2->ddomain) &&
  701            !strcmp(co->path, c2->path) &&
  702            !strcmp(co->name, c2->name)) {
  703         HTList_removeObject(cookie_list, c2);
  704         freeCookie(c2);
  705         c2 = NULL;
  706         total_cookies--;
  707         Replacement = TRUE;
  708 
  709     } else if ((c2) && (c2->pathlen) >= (co->pathlen)) {
  710         /*
  711          * This comparison determines the (tentative) position of the new
  712          * cookie in the list such that it comes before existing cookies
  713          * with a less specific path, but after existing cookies of equal
  714          * (or greater) path length.  Thus it should normally preserve the
  715          * order of new cookies with the same path as they are received,
  716          * although this is not required.
  717          *
  718          * From RFC 2109 4.3.4:
  719          *
  720          * If multiple cookies satisfy the criteria above, they are ordered
  721          * in the Cookie header such that those with more specific Path
  722          * attributes precede those with less specific.  Ordering with
  723          * respect to other attributes (e.g., Domain) is unspecified.
  724          */
  725         pos++;
  726     }
  727     hl = next;
  728     }
  729 
  730     /*
  731      * Don't bother to add the cookie if it's already expired.
  732      */
  733     if ((co->flags & COOKIE_FLAG_EXPIRES_SET) && co->expires <= now) {
  734     freeCookie(co);
  735     co = NULL;
  736 
  737     /*
  738      * Don't add the cookie if we're over the domain's limit.  - FM
  739      */
  740     } else if (HTList_count(cookie_list) > max_cookies_domain) {
  741     CTrace((tfp,
  742         "store_cookie: Domain's cookie limit exceeded!  Rejecting cookie.\n"));
  743     freeCookie(co);
  744     co = NULL;
  745 
  746     /*
  747      * Don't add the cookie if we're over the total cookie limit.  - FM
  748      */
  749     } else if (total_cookies > max_cookies_global) {
  750     CTrace((tfp,
  751         "store_cookie: Total cookie limit exceeded!  Rejecting cookie.\n"));
  752     freeCookie(co);
  753     co = NULL;
  754 
  755     /*
  756      * Don't add the cookie if the value is NULL. - BJP
  757      */
  758     /*
  759      * Presence of value is now needed (indicated normally by '='),
  760      * but it can now be an empty string.
  761      * - kw 1999-06-24
  762      */
  763     } else if (co->value == NULL) { /* should not happen - kw */
  764     CTrace((tfp, "store_cookie: Value is NULL! Not storing cookie.\n"));
  765     freeCookie(co);
  766     co = NULL;
  767 
  768     /*
  769      * If it's a replacement for a cookie that had not expired, and never
  770      * allow has not been set, add it again without confirmation.  - FM
  771      */
  772     } else if ((Replacement == TRUE) && de->bv != REJECT_ALWAYS) {
  773     HTList_insertObjectAt(cookie_list, co, pos);
  774     total_cookies++;
  775 
  776     /*
  777      * Get confirmation if we need it, and add cookie if confirmed or
  778      * 'allow' is set to always.  - FM
  779      *
  780      * Cookies read from file are accepted without confirmation prompting.
  781      * (Prompting may actually not be possible if LYLoadCookies is called
  782      * before curses is setup.) Maybe this should instead depend on
  783      * LYSetCookies and/or LYCookieAcceptDomains and/or
  784      * LYCookieRejectDomains and/or LYAcceptAllCookies and/or some other
  785      * settings.  -kw
  786      */
  787     } else if ((co->flags & COOKIE_FLAG_FROM_FILE)
  788            || HTConfirmCookie(de, hostname, co->name, co->value)) {
  789     /*
  790      * Insert the new cookie so that more specific paths (longer
  791      * pathlen) come first in the list. - kw
  792      */
  793     HTList_insertObjectAt(cookie_list, co, pos);
  794     total_cookies++;
  795     } else {
  796     freeCookie(co);
  797     co = NULL;
  798     }
  799 }
  800 
  801 /*
  802  *  Scan a domain's cookie_list for any cookies we should
  803  *  include in a Cookie: request header. - AK & FM
  804  */
  805 static char *scan_cookie_sublist(char *hostname,
  806                  char *path,
  807                  int port,
  808                  HTList *sublist,
  809                  char *header,
  810                  int secure)
  811 {
  812     HTList *hl, *next;
  813     cookie *co;
  814     time_t now = time(NULL);
  815     char crlftab[8];
  816 
  817     sprintf(crlftab, "%c%c%c", CR, LF, '\t');
  818     for (hl = sublist; hl != NULL; hl = next) {
  819     next = hl->next;
  820     co = (cookie *) hl->object;
  821 
  822     if (co == NULL) {
  823         continue;
  824     }
  825 
  826     /* speed-up host_matches() and limit trace output */
  827     if (LYstrstr(hostname, co->ddomain) != NULL) {
  828         CTrace((tfp, "Checking cookie %p %s=%s\n",
  829             (void *) hl,
  830             (co->name ? co->name : "(no name)"),
  831             (co->value ? co->value : "(no value)")));
  832         CTrace((tfp, "\t%s %s %d %s %s %d%s\n",
  833             hostname,
  834             (co->ddomain ? co->ddomain : "(no domain)"),
  835             domain_matches(hostname, co->ddomain),
  836             path, co->path,
  837             (co->pathlen > 0)
  838             ? !is_prefix(co->path, path)
  839             : 0,
  840             (co->flags & COOKIE_FLAG_SECURE)
  841             ? " secure"
  842             : ""));
  843     }
  844     /*
  845      * Check if this cookie has expired, and if so, delete it.
  846      */
  847     if ((co->flags & COOKIE_FLAG_EXPIRES_SET) &&
  848         co->expires <= now) {
  849         next = hl->next;
  850         HTList_removeObject(sublist, co);
  851         freeCookie(co);
  852         total_cookies--;
  853         if (next)
  854         continue;
  855         break;
  856     }
  857 
  858     /*
  859      * Check if we have a unexpired match, and handle if we do.
  860      */
  861     if (co->domain != 0 &&
  862         co->name != 0 &&
  863         domain_matches(hostname, co->ddomain) &&
  864         (co->pathlen == 0 || is_prefix(co->path, path))) {
  865         /*
  866          * Skip if the secure flag is set and we don't have a secure
  867          * connection.  HTTP.c presently treats only SSL connections as
  868          * secure.  - FM
  869          */
  870         if ((co->flags & COOKIE_FLAG_SECURE) && secure == FALSE) {
  871         continue;
  872         }
  873 
  874         /*
  875          * Skip if we have a port list and the current port is not listed.
  876          * - FM
  877          */
  878         if (USE_RFC_2965
  879         && co->PortList
  880         && !port_matches(port, co->PortList)) {
  881         continue;
  882         }
  883 
  884         /*
  885          * Start or append to the request header.
  886          */
  887         if (header == NULL) {
  888         if (co->version > 0) {
  889             /*
  890              * For Version 1 (or greater) cookies, the version number
  891              * goes before the first cookie.
  892              */
  893             HTSprintf0(&header, "$Version=\"%d\"; ", co->version);
  894         }
  895         } else {
  896         /*
  897          * There's already cookie data there, so add a separator
  898          * (always use a semi-colon for "backward compatibility").  -
  899          * FM
  900          */
  901         StrAllocCat(header, "; ");
  902         /*
  903          * Check if we should fold the header.  - FM
  904          */
  905 
  906         /*
  907          * Section 2.2 of RFC1945 says:
  908          *
  909          * HTTP/1.0 headers may be folded onto multiple lines if each
  910          * continuation line begins with a space or horizontal tab.
  911          * All linear whitespace, including folding, has the same
  912          * semantics as SP.  [...] However, folding of header lines is
  913          * not expected by some applications, and should not be
  914          * generated by HTTP/1.0 applications.
  915          *
  916          * This code was causing problems.  Let's not use it.  -BJP
  917          */
  918 
  919         /* if (len > 800) { */
  920         /*    StrAllocCat(header, crlftab); */
  921         /* } */
  922 
  923         }
  924         /*
  925          * Include the cookie name=value pair.
  926          */
  927         StrAllocCat(header, co->name);
  928         StrAllocCat(header, "=");
  929         if (co->quoted) {
  930         StrAllocCat(header, "\"");
  931         }
  932         StrAllocCat(header, co->value);
  933         if (co->quoted) {
  934         StrAllocCat(header, "\"");
  935         }
  936         /*
  937          * For Version 1 (or greater) cookies, add $PATH, $PORT and/or
  938          * $DOMAIN attributes for the cookie if they were specified via a
  939          * server reply header.  - FM
  940          */
  941         if (co->version > 0) {
  942         if (co->path && (co->flags & COOKIE_FLAG_PATH_SET)) {
  943             HTSprintf(&header, "; $Path=\"%s\"", co->path);
  944         }
  945         if (co->PortList && isdigit(UCH(*co->PortList))) {
  946             HTSprintf(&header, "; $Port=\"%s\"", co->PortList);
  947         }
  948         if (co->domain && (co->flags & COOKIE_FLAG_DOMAIN_SET)) {
  949             HTSprintf(&header, "; $Domain=\"%s\"", co->domain);
  950         }
  951         }
  952     }
  953     }
  954 
  955     return (header);
  956 }
  957 
  958 /*
  959  * Presence of value is needed (indicated normally by '=') to start a cookie,
  960  * but it can be an empty string.  - kw 1999-06-24
  961  */
  962 static char *alloc_attr_value(const char *value_start,
  963                   const char *value_end)
  964 {
  965     char *value = NULL;
  966 
  967     if (value_start && value_end >= value_start) {
  968     int value_len = (int) (value_end - value_start);
  969 
  970     if (value_len > max_cookies_buffer) {
  971         value_len = max_cookies_buffer;
  972     }
  973     value = typecallocn(char, (unsigned) value_len + 1);
  974 
  975     if (value == NULL)
  976         outofmem(__FILE__, "LYProcessSetCookies");
  977     LYStrNCpy(value, value_start, value_len);
  978     }
  979     return value;
  980 }
  981 
  982 #define FLAGS_INVALID_PORT 1
  983 #define FLAGS_KNOWN_ATTR   2
  984 #define FLAGS_MAXAGE_ATTR  4
  985 
  986 #define is_attr(s, len) attr_len == len && !strncasecomp(attr_start, s, len)
  987 
  988 /*
  989  * Attribute-names are matched ignoring case.
  990  *
  991  *  Attribute   RFC-2109 (1997) RFC-2965 (2000) RFC-6265 (2011)
  992  *  ---------------------------------------------------------------
  993  *  comment     yes     yes     -
  994  *  commentURL  -       yes     -
  995  *  discard     -       yes     -
  996  *  domain      yes     yes     yes
  997  *  expires     yes     yes     yes
  998  *  httponly    -       -       yes
  999  *  max-age     yes     yes     yes
 1000  *  path        yes     yes     yes
 1001  *  port        -       yes     -
 1002  *  secure      yes     yes     yes
 1003  *  version     yes     yes     -
 1004  *  ---------------------------------------------------------------
 1005  */
 1006 static unsigned parse_attribute(unsigned flags,
 1007                 cookie * cur_cookie,
 1008                 int *cookie_len,
 1009                 const char *attr_start,
 1010                 int attr_len,
 1011                 char *value,
 1012                 const char *address,
 1013                 char *hostname,
 1014                 int port)
 1015 {
 1016     BOOLEAN known_attr = NO;
 1017     int url_type;
 1018 
 1019     CTrace((tfp, "parse_attribute %.*s\n", attr_len, attr_start));
 1020 
 1021     flags &= (unsigned) (~FLAGS_KNOWN_ATTR);
 1022     if (is_attr("secure", 6)) {
 1023     if (value == NULL) {
 1024         known_attr = YES;
 1025         if (cur_cookie != NULL) {
 1026         cur_cookie->flags |= COOKIE_FLAG_SECURE;
 1027         }
 1028     } else {
 1029         /*
 1030          * If secure has a value, assume someone misused it as cookie name.
 1031          * - FM
 1032          */
 1033         known_attr = NO;
 1034     }
 1035     } else if (USE_RFC_6265 && is_attr("httponly", 8)) {
 1036     if (value == NULL) {
 1037         known_attr = YES;   /* known, but irrelevant to lynx */
 1038     } else {
 1039         known_attr = NO;
 1040     }
 1041     } else if (USE_RFC_2965 && is_attr("discard", 7)) {
 1042     if (value == NULL) {
 1043         known_attr = YES;
 1044         if (cur_cookie != NULL) {
 1045         cur_cookie->flags |= COOKIE_FLAG_DISCARD;
 1046         }
 1047     } else {
 1048         /*
 1049          * If discard has a value, assume someone used it as a cookie name.
 1050          * - FM
 1051          */
 1052         known_attr = NO;
 1053     }
 1054     } else if ((USE_RFC_2109 || USE_RFC_2965) && is_attr("comment", 7)) {
 1055     known_attr = YES;
 1056     if (cur_cookie != NULL && value &&
 1057     /*
 1058      * Don't process a repeat comment.  - FM
 1059      */
 1060         cur_cookie->comment == NULL) {
 1061         StrAllocCopy(cur_cookie->comment, value);
 1062         *cookie_len += (int) strlen(cur_cookie->comment);
 1063     }
 1064     } else if (USE_RFC_2965 && is_attr("commentURL", 10)) {
 1065     known_attr = YES;
 1066     if (cur_cookie != NULL && value &&
 1067     /*
 1068      * Don't process a repeat commentURL.  - FM
 1069      */
 1070         cur_cookie->commentURL == NULL) {
 1071         /*
 1072          * We should get only absolute URLs as values, but will resolve
 1073          * versus the request's URL just in case.  - FM
 1074          */
 1075         cur_cookie->commentURL = HTParse(value,
 1076                          address,
 1077                          PARSE_ALL);
 1078         /*
 1079          * Accept only URLs for http or https servers.  - FM
 1080          */
 1081         if ((url_type = is_url(cur_cookie->commentURL)) &&
 1082         (url_type == HTTP_URL_TYPE ||
 1083          url_type == HTTPS_URL_TYPE)) {
 1084         *cookie_len += (int) strlen(cur_cookie->commentURL);
 1085         } else {
 1086         CTrace((tfp,
 1087             "LYProcessSetCookies: Rejecting commentURL value '%s'\n",
 1088             cur_cookie->commentURL));
 1089         FREE(cur_cookie->commentURL);
 1090         }
 1091     }
 1092     } else if (is_attr("domain", 6)) {
 1093     known_attr = YES;
 1094     if (cur_cookie != NULL && value &&
 1095     /*
 1096      * Don't process a repeat domain.  - FM
 1097      */
 1098         !(cur_cookie->flags & COOKIE_FLAG_DOMAIN_SET)) {
 1099         *cookie_len -= (int) strlen(cur_cookie->domain);
 1100         /*
 1101          * If the value does not have a lead dot, but does have an embedded
 1102          * dot, and is not an exact match to the hostname, nor is a numeric
 1103          * IP address, add a lead dot.  Otherwise, use the value as is.  -
 1104          * FM (domains - case insensitive).
 1105          */
 1106         if (value[0] != '.' && value[0] != '\0' &&
 1107         value[1] != '\0' && strcasecomp(value, hostname)) {
 1108         char *ptr = StrChr(value, '.');
 1109 
 1110         if (ptr != NULL && ptr[1] != '\0') {
 1111             ptr = value;
 1112             while (*ptr == '.' ||
 1113                isdigit(UCH(*ptr)))
 1114             ptr++;
 1115             if (*ptr != '\0') {
 1116             CTrace((tfp,
 1117                 "LYProcessSetCookies: Adding lead dot for domain value '%s'\n",
 1118                 value));
 1119             HTSprintf0(&(cur_cookie->domain), ".%s", value);
 1120             } else {
 1121             StrAllocCopy(cur_cookie->domain, value);
 1122             }
 1123         } else {
 1124             StrAllocCopy(cur_cookie->domain, value);
 1125         }
 1126         } else {
 1127         StrAllocCopy(cur_cookie->domain, value);
 1128         }
 1129         *cookie_len += (int) strlen(cur_cookie->domain);
 1130         cur_cookie->flags |= COOKIE_FLAG_DOMAIN_SET;
 1131         SetCookieDomain(cur_cookie, cur_cookie->domain);
 1132     }
 1133     } else if (is_attr("path", 4)) {
 1134     known_attr = YES;
 1135     if (cur_cookie != NULL && value &&
 1136     /*
 1137      * Don't process a repeat path.  - FM
 1138      */
 1139         !(cur_cookie->flags & COOKIE_FLAG_PATH_SET)) {
 1140         *cookie_len -= (int) strlen(cur_cookie->path);
 1141         StrAllocCopy(cur_cookie->path, value);
 1142         *cookie_len += (cur_cookie->pathlen = (int) strlen(cur_cookie->path));
 1143         cur_cookie->flags |= COOKIE_FLAG_PATH_SET;
 1144         CTrace((tfp, " ->%.*s\n", cur_cookie->pathlen, cur_cookie->path));
 1145     }
 1146     } else if (USE_RFC_2965 && is_attr("port", 4)) {
 1147     if (cur_cookie != NULL && value &&
 1148     /*
 1149      * Don't process a repeat port.  - FM
 1150      */
 1151         cur_cookie->PortList == NULL) {
 1152         char *cp = value;
 1153 
 1154         while ((*cp != '\0') &&
 1155            (isdigit(UCH(*cp)) ||
 1156             *cp == ',' || *cp == ' ')) {
 1157         cp++;
 1158         }
 1159         if (*cp == '\0') {
 1160         if (!port_matches(port, value)) {
 1161             flags |= FLAGS_INVALID_PORT;
 1162         } else {
 1163             StrAllocCopy(cur_cookie->PortList, value);
 1164             *cookie_len += (int) strlen(cur_cookie->PortList);
 1165             CTrace((tfp, " ->%s\n", cur_cookie->PortList));
 1166         }
 1167         known_attr = YES;
 1168         } else {
 1169         known_attr = NO;
 1170         }
 1171     } else if (cur_cookie != NULL) {
 1172         /*
 1173          * Don't process a repeat port.  - FM
 1174          */
 1175         if (cur_cookie->PortList == NULL) {
 1176         HTSprintf0(&(cur_cookie->PortList), "%d", port);
 1177         *cookie_len += (int) strlen(cur_cookie->PortList);
 1178         }
 1179         known_attr = YES;
 1180     }
 1181     } else if ((USE_RFC_2109 || USE_RFC_2965) && is_attr("version", 7)) {
 1182     known_attr = YES;
 1183     if (cur_cookie != NULL && value &&
 1184     /*
 1185      * Don't process a repeat version.  - FM
 1186      */
 1187         cur_cookie->version < 1) {
 1188         int temp = (int) strtol(value, NULL, 10);
 1189 
 1190         if (errno != -ERANGE) {
 1191         cur_cookie->version = temp;
 1192         }
 1193     }
 1194     } else if (is_attr("max-age", 7)) {
 1195     known_attr = YES;
 1196     /*
 1197      * Don't process a repeat max-age.  - FM
 1198      */
 1199     if (cur_cookie != NULL && value &&
 1200         !(flags & FLAGS_MAXAGE_ATTR)) {
 1201         long temp = strtol(value, NULL, 10);
 1202 
 1203         cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
 1204         if (errno == -ERANGE) {
 1205         cur_cookie->expires = (time_t) 0;
 1206         } else {
 1207         cur_cookie->expires = (time(NULL) + temp);
 1208         CTrace((tfp, "LYSetCookie: expires %" PRI_time_t ", %s",
 1209             CAST_time_t (cur_cookie->expires),
 1210             ctime(&cur_cookie->expires)));
 1211         }
 1212         flags |= FLAGS_MAXAGE_ATTR;
 1213     }
 1214     } else if (is_attr("expires", 7)) {
 1215     /*
 1216      * Convert an 'expires' attribute value if we haven't received a
 1217      * 'max-age'.  Note that 'expires' should not be used in Version 1
 1218      * cookies, but it might be used for "backward compatibility", and, in
 1219      * turn, ill-informed people surely would start using it instead of,
 1220      * rather than in addition to, 'max-age'.  - FM
 1221      */
 1222     known_attr = YES;
 1223     if ((cur_cookie != NULL && !(flags & FLAGS_MAXAGE_ATTR)) &&
 1224         !(cur_cookie->flags & COOKIE_FLAG_EXPIRES_SET)) {
 1225         if (value) {
 1226         cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
 1227         cur_cookie->expires = LYmktime(value, FALSE);
 1228         if (cur_cookie->expires > 0) {
 1229             CTrace((tfp, "LYSetCookie: expires %" PRI_time_t ", %s",
 1230                 CAST_time_t (cur_cookie->expires),
 1231                 ctime(&cur_cookie->expires)));
 1232         }
 1233         }
 1234     }
 1235     }
 1236     if (known_attr)
 1237     flags |= FLAGS_KNOWN_ATTR;
 1238     return flags;
 1239 }
 1240 
 1241 /*
 1242  *  Process potentially concatenated Set-Cookie2 and/or Set-Cookie
 1243  *  headers. - FM
 1244  */
 1245 static void LYProcessSetCookies(const char *SetCookie,
 1246                 const char *SetCookie2,
 1247                 const char *address,
 1248                 char *hostname,
 1249                 char *path,
 1250                 int port)
 1251 {
 1252     const char *p, *attr_start, *attr_end, *value_start, *value_end;
 1253     HTList *CombinedCookies = NULL, *cl = NULL;
 1254     cookie *cur_cookie = NULL, *co = NULL;
 1255     int cookie_len = 0;
 1256     int NumCookies = 0;
 1257     BOOL Quoted = FALSE;
 1258     unsigned parse_flags = 0;
 1259 
 1260     if (isEmpty(SetCookie) &&
 1261     isEmpty(SetCookie2)) {
 1262     /*
 1263      * Yuk!  Garbage in, so nothing out.  - FM
 1264      */
 1265     return;
 1266     }
 1267 
 1268     /*
 1269      * If we have both Set-Cookie and Set-Cookie2 headers.  process the
 1270      * Set-Cookie2 header.  Otherwise, process whichever of the two headers we
 1271      * do have.  Note that if more than one instance of a valued attribute for
 1272      * the same cookie is encountered, the value for the first instance is
 1273      * retained.
 1274      */
 1275     CombinedCookies = HTList_new();
 1276 
 1277     /*
 1278      * Process the Set-Cookie2 header, if present and not zero-length, adding
 1279      * each cookie to the CombinedCookies list.  - FM
 1280      */
 1281     p = NonNull(SetCookie2);
 1282     if (SetCookie && *p) {
 1283     CTrace((tfp, "LYProcessSetCookies: Using Set-Cookie2 header.\n"));
 1284     }
 1285     while (NumCookies <= max_cookies_domain && *p) {
 1286     value_start = value_end = NULL;
 1287     p = LYSkipCBlanks(p);
 1288     /*
 1289      * Get the attribute name.
 1290      */
 1291     attr_start = p;
 1292     while (*p != '\0' && !isspace(UCH(*p)) &&
 1293            *p != '=' && *p != ';' && *p != ',')
 1294         p++;
 1295     attr_end = p;
 1296     p = LYSkipCBlanks(p);
 1297 
 1298     /*
 1299      * Check for an '=' delimiter, or an 'expires' name followed by white,
 1300      * since Netscape's bogus parser doesn't require an '=' delimiter, and
 1301      * 'expires' attributes are being encountered without them.  These
 1302      * shouldn't be in a Set-Cookie2 header, but we'll assume it's an
 1303      * expires attribute rather a cookie with that name, since the
 1304      * attribute mistake rather than name mistake seems more likely to be
 1305      * made by providers.  - FM
 1306      */
 1307     if (*p == '=' ||
 1308         !strncasecomp(attr_start, "Expires", 7)) {
 1309         /*
 1310          * Get the value string.
 1311          */
 1312         if (*p == '=') {
 1313         p++;
 1314         }
 1315         p = LYSkipCBlanks(p);
 1316         /*
 1317          * Hack alert!  We must handle Netscape-style cookies with
 1318          *          "Expires=Mon, 01-Jan-96 13:45:35 GMT" or
 1319          *          "Expires=Mon,  1 Jan 1996 13:45:35 GMT".
 1320          * No quotes, but there are spaces.  Argh...  Anyway, we know it
 1321          * will have at least 3 space separators within it, and two dashes
 1322          * or two more spaces, so this code looks for a space after the 5th
 1323          * space separator or dash to mark the end of the value.  - FM
 1324          */
 1325         if ((attr_end - attr_start) == 7 &&
 1326         !strncasecomp(attr_start, "Expires", 7)) {
 1327         int spaces = 6;
 1328 
 1329         value_start = p;
 1330         if (isdigit(UCH(*p))) {
 1331             /*
 1332              * No alphabetic day field.  - FM
 1333              */
 1334             spaces--;
 1335         } else {
 1336             /*
 1337              * Skip the alphabetic day field.  - FM
 1338              */
 1339             while (*p != '\0' && isalpha(UCH(*p))) {
 1340             p++;
 1341             }
 1342             while (*p == ',' || isspace(UCH(*p))) {
 1343             p++;
 1344             }
 1345             spaces--;
 1346         }
 1347         while (*p != '\0' && *p != ';' && *p != ',' && spaces) {
 1348             p++;
 1349             if (isspace(UCH(*p))) {
 1350             while (isspace(UCH(*(p + 1))))
 1351                 p++;
 1352             spaces--;
 1353             } else if (*p == '-') {
 1354             spaces--;
 1355             }
 1356         }
 1357         value_end = p;
 1358         /*
 1359          * Hack Alert!  The port attribute can take a comma separated
 1360          * list of numbers as a value, and such values should be
 1361          * quoted, but if not, make sure we don't treat a number in the
 1362          * list as the start of a new cookie.  - FM
 1363          */
 1364         } else if ((attr_end - attr_start) == 4 &&
 1365                !strncasecomp(attr_start, "port", 4) &&
 1366                isdigit(UCH(*p))) {
 1367         /*
 1368          * The value starts as an unquoted number.
 1369          */
 1370         const char *cp, *cp1;
 1371 
 1372         value_start = p;
 1373         while (1) {
 1374             while (isdigit(UCH(*p)))
 1375             p++;
 1376             value_end = p;
 1377             p = LYSkipCBlanks(p);
 1378             if (*p == '\0' || *p == ';')
 1379             break;
 1380             if (*p == ',') {
 1381             cp = LYSkipCBlanks(p + 1);
 1382             if (*cp != '\0' && isdigit(UCH(*cp))) {
 1383                 cp1 = cp;
 1384                 while (isdigit(UCH(*cp1)))
 1385                 cp1++;
 1386                 cp1 = LYSkipCBlanks(cp1);
 1387                 if (*cp1 == '\0' || *cp1 == ',' || *cp1 == ';') {
 1388                 p = cp;
 1389                 continue;
 1390                 }
 1391             }
 1392             }
 1393             while (*p != '\0' && *p != ';' && *p != ',')
 1394             p++;
 1395             value_end = p;
 1396             /*
 1397              * Trim trailing spaces.
 1398              */
 1399             if ((value_end > value_start) &&
 1400             isspace(UCH(*(value_end - 1)))) {
 1401             value_end--;
 1402             while ((value_end > (value_start + 1)) &&
 1403                    isspace(UCH(*value_end)) &&
 1404                    isspace(UCH(*(value_end - 1)))) {
 1405                 value_end--;
 1406             }
 1407             }
 1408             break;
 1409         }
 1410         } else if (*p == '"') {
 1411         BOOLEAN escaped = FALSE;
 1412 
 1413         /*
 1414          * It looks like quoted string.
 1415          */
 1416         p++;
 1417         value_start = p;
 1418         while (*p != '\0' && (*p != '"' || escaped)) {
 1419             escaped = (BOOL) (!escaped && *p == '\\');
 1420             p++;
 1421         }
 1422         if (p != value_start && *p == '"' && !escaped) {
 1423             value_end = p;
 1424             p++;
 1425             Quoted = TRUE;
 1426         } else {
 1427             value_start--;
 1428             value_end = p;
 1429             if (*p)
 1430             p++;
 1431             Quoted = FALSE;
 1432         }
 1433         } else {
 1434         /*
 1435          * Otherwise, it's an unquoted string.
 1436          */
 1437         value_start = p;
 1438         while (*p != '\0' && *p != ';' && *p != ',')
 1439             p++;
 1440         value_end = p;
 1441         /*
 1442          * Trim trailing spaces.
 1443          */
 1444         if ((value_end > value_start) &&
 1445             isspace(UCH(*(value_end - 1)))) {
 1446             value_end--;
 1447             while ((value_end > (value_start + 1)) &&
 1448                isspace(UCH(*value_end)) &&
 1449                isspace(UCH(*(value_end - 1)))) {
 1450             value_end--;
 1451             }
 1452         }
 1453         }
 1454     }
 1455 
 1456     /*
 1457      * Check for a separator character, and skip it.
 1458      */
 1459     if (*p == ';' || *p == ',')
 1460         p++;
 1461 
 1462     /*
 1463      * Now, we can handle this attribute/value pair.
 1464      */
 1465     if (attr_end > attr_start) {
 1466         char *value = alloc_attr_value(value_start, value_end);
 1467 
 1468         parse_flags = parse_attribute(parse_flags,
 1469                       cur_cookie,
 1470                       &cookie_len,
 1471                       attr_start,
 1472                       (int) (attr_end - attr_start),
 1473                       value,
 1474                       address,
 1475                       hostname,
 1476                       port);
 1477 
 1478         /*
 1479          * Presence of value is needed (indicated normally by '='),
 1480          * but it can be an empty string. - kw 1999-06-24
 1481          */
 1482         if (!(parse_flags & FLAGS_KNOWN_ATTR)
 1483         && value
 1484         && value_end >= value_start) {
 1485         /*
 1486          * If we've started a cookie, and it's not too big, save it in
 1487          * the CombinedCookies list.  - FM
 1488          */
 1489         if (cookie_len <= max_cookies_buffer
 1490             && cur_cookie != NULL
 1491             && !(parse_flags & FLAGS_INVALID_PORT)) {
 1492             AssumeCookieVersion(cur_cookie);
 1493             HTList_appendObject(CombinedCookies, cur_cookie);
 1494         } else if (cur_cookie != NULL) {
 1495             CTrace((tfp,
 1496                 "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
 1497                 (cur_cookie->name ?
 1498                  cur_cookie->name : "[no name]"),
 1499                 (cur_cookie->value ?
 1500                  cur_cookie->value : "[no value]")));
 1501             CTrace((tfp,
 1502                 (parse_flags & FLAGS_INVALID_PORT) ?
 1503                 "                     due to excessive length!\n"
 1504                 : "                     due to invalid port!\n"));
 1505             if (parse_flags & FLAGS_INVALID_PORT) {
 1506             NumCookies--;
 1507             }
 1508             freeCookie(cur_cookie);
 1509             cur_cookie = NULL;
 1510         }
 1511         /*
 1512          * Start a new cookie.  - FM
 1513          */
 1514         cur_cookie = newCookie();
 1515         cookie_len = 0;
 1516         NumCookies++;
 1517         MemAllocCopy(&(cur_cookie->name), attr_start, attr_end);
 1518         cookie_len += (int) strlen(cur_cookie->name);
 1519         MemAllocCopy(&(cur_cookie->value), value_start, value_end);
 1520         cookie_len += (int) strlen(cur_cookie->value);
 1521         StrAllocCopy(cur_cookie->domain, hostname);
 1522         cookie_len += (int) strlen(hostname);
 1523         StrAllocCopy(cur_cookie->path, path);
 1524         cookie_len += (cur_cookie->pathlen = (int) strlen(cur_cookie->path));
 1525         cur_cookie->port = port;
 1526         parse_flags = 0;
 1527         cur_cookie->quoted = TRUE;
 1528         SetCookieDomain(cur_cookie, hostname);
 1529         }
 1530         FREE(value);
 1531     }
 1532     }
 1533 
 1534     /*
 1535      * Add any final SetCookie2 cookie to the CombinedCookie list if we are
 1536      * within the length limit.  - FM
 1537      */
 1538     if (NumCookies <= max_cookies_domain
 1539     && cookie_len <= max_cookies_buffer
 1540     && cur_cookie != NULL && !(parse_flags & FLAGS_INVALID_PORT)) {
 1541     AssumeCookieVersion(cur_cookie);
 1542     HTList_appendObject(CombinedCookies, cur_cookie);
 1543     } else if (cur_cookie != NULL && !(parse_flags & FLAGS_INVALID_PORT)) {
 1544     CTrace((tfp, "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
 1545         (cur_cookie->name ? cur_cookie->name : "[no name]"),
 1546         (cur_cookie->value ? cur_cookie->value : "[no value]")));
 1547     CTrace((tfp, "                     due to excessive %s%s%s\n",
 1548         (cookie_len > max_cookies_buffer ? "length" : ""),
 1549         (cookie_len > max_cookies_buffer &&
 1550          NumCookies > max_cookies_domain
 1551          ? " and "
 1552          : ""),
 1553         (NumCookies > max_cookies_domain ? "number!\n" : "!\n")));
 1554     freeCookie(cur_cookie);
 1555     cur_cookie = NULL;
 1556     } else if (cur_cookie != NULL) {    /* invalidport */
 1557     CTrace((tfp, "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
 1558         (cur_cookie->name ? cur_cookie->name : "[no name]"),
 1559         (cur_cookie->value ? cur_cookie->value : "[no value]")));
 1560     CTrace((tfp, "                     due to invalid port!\n"));
 1561     NumCookies--;
 1562     freeCookie(cur_cookie);
 1563     cur_cookie = NULL;
 1564     }
 1565 
 1566     /*
 1567      * Process the Set-Cookie header, if no non-zero-length Set-Cookie2 header
 1568      * was present.  - FM
 1569      */
 1570     cookie_len = 0;
 1571     NumCookies = 0;
 1572     cur_cookie = NULL;
 1573     p = ((SetCookie && isEmpty(SetCookie2)) ? SetCookie : "");
 1574     if (SetCookie2 && *p) {
 1575     CTrace((tfp, "LYProcessSetCookies: Using Set-Cookie header.\n"));
 1576     }
 1577     while (NumCookies <= max_cookies_domain && *p) {
 1578     value_start = value_end = NULL;
 1579     p = LYSkipCBlanks(p);
 1580     /*
 1581      * Get the attribute name.
 1582      */
 1583     attr_start = p;
 1584     while (*p != '\0' && !isspace(UCH(*p)) &&
 1585            *p != '=' && *p != ';' && *p != ',')
 1586         p++;
 1587     attr_end = p;
 1588     p = LYSkipCBlanks(p);
 1589 
 1590     /*
 1591      * Check for an '=' delimiter, or an 'expires' name followed by white,
 1592      * since Netscape's bogus parser doesn't require an '=' delimiter, and
 1593      * 'expires' attributes are being encountered without them.  - FM
 1594      */
 1595     if (*p == '=' ||
 1596         !strncasecomp(attr_start, "Expires", 7)) {
 1597         /*
 1598          * Get the value string.
 1599          */
 1600         if (*p == '=') {
 1601         p++;
 1602         }
 1603         p = LYSkipCBlanks(p);
 1604         /*
 1605          * Hack alert!  We must handle Netscape-style cookies with
 1606          *          "Expires=Mon, 01-Jan-96 13:45:35 GMT" or
 1607          *          "Expires=Mon,  1 Jan 1996 13:45:35 GMT".
 1608          * No quotes, but there are spaces.  Argh...  Anyway, we know it
 1609          * will have at least 3 space separators within it, and two dashes
 1610          * or two more spaces, so this code looks for a space after the 5th
 1611          * space separator or dash to mark the end of the value.  - FM
 1612          */
 1613         if ((attr_end - attr_start) == 7 &&
 1614         !strncasecomp(attr_start, "Expires", 7)) {
 1615         int spaces = 6;
 1616 
 1617         value_start = p;
 1618         if (isdigit(UCH(*p))) {
 1619             /*
 1620              * No alphabetic day field.  - FM
 1621              */
 1622             spaces--;
 1623         } else {
 1624             /*
 1625              * Skip the alphabetic day field.  - FM
 1626              */
 1627             while (*p != '\0' && isalpha(UCH(*p))) {
 1628             p++;
 1629             }
 1630             while (*p == ',' || isspace(UCH(*p))) {
 1631             p++;
 1632             }
 1633             spaces--;
 1634         }
 1635         while (*p != '\0' && *p != ';' && *p != ',' && spaces) {
 1636             p++;
 1637             if (isspace(UCH(*p))) {
 1638             while (isspace(UCH(*(p + 1))))
 1639                 p++;
 1640             spaces--;
 1641             } else if (*p == '-') {
 1642             spaces--;
 1643             }
 1644         }
 1645         value_end = p;
 1646         /*
 1647          * Hack Alert!  The port attribute can take a comma separated
 1648          * list of numbers as a value, and such values should be
 1649          * quoted, but if not, make sure we don't treat a number in the
 1650          * list as the start of a new cookie.  - FM
 1651          */
 1652         } else if ((attr_end - attr_start) == 4 &&
 1653                !strncasecomp(attr_start, "port", 4) &&
 1654                isdigit(UCH(*p))) {
 1655         /*
 1656          * The value starts as an unquoted number.
 1657          */
 1658         const char *cp, *cp1;
 1659 
 1660         value_start = p;
 1661         while (1) {
 1662             while (isdigit(UCH(*p)))
 1663             p++;
 1664             value_end = p;
 1665             p = LYSkipCBlanks(p);
 1666             if (*p == '\0' || *p == ';')
 1667             break;
 1668             if (*p == ',') {
 1669             cp = LYSkipCBlanks(p + 1);
 1670             if (*cp != '\0' && isdigit(UCH(*cp))) {
 1671                 cp1 = cp;
 1672                 while (isdigit(UCH(*cp1)))
 1673                 cp1++;
 1674                 cp1 = LYSkipCBlanks(cp1);
 1675                 if (*cp1 == '\0' || *cp1 == ',' || *cp1 == ';') {
 1676                 p = cp;
 1677                 continue;
 1678                 }
 1679             }
 1680             }
 1681             while (*p != '\0' && *p != ';' && *p != ',')
 1682             p++;
 1683             value_end = p;
 1684             /*
 1685              * Trim trailing spaces.
 1686              */
 1687             if ((value_end > value_start) &&
 1688             isspace(UCH(*(value_end - 1)))) {
 1689             value_end--;
 1690             while ((value_end > (value_start + 1)) &&
 1691                    isspace(UCH(*value_end)) &&
 1692                    isspace(UCH(*(value_end - 1)))) {
 1693                 value_end--;
 1694             }
 1695             }
 1696             break;
 1697         }
 1698         } else if (*p == '"') {
 1699         BOOLEAN escaped = FALSE;
 1700 
 1701         /*
 1702          * It looks like quoted string.
 1703          */
 1704         p++;
 1705         value_start = p;
 1706         while (*p != '\0' && (*p != '"' || escaped)) {
 1707             escaped = (BOOL) (!escaped && *p == '\\');
 1708             p++;
 1709         }
 1710         if (p != value_start && *p == '"' && !escaped) {
 1711             value_end = p;
 1712             p++;
 1713             Quoted = TRUE;
 1714         } else {
 1715             value_start--;
 1716             value_end = p;
 1717             if (*p)
 1718             p++;
 1719             Quoted = FALSE;
 1720         }
 1721         } else {
 1722         /*
 1723          * Otherwise, it's an unquoted string.
 1724          */
 1725         value_start = p;
 1726         while (*p != '\0' && *p != ';' && *p != ',')
 1727             p++;
 1728         value_end = p;
 1729         /*
 1730          * Trim trailing spaces.
 1731          */
 1732         if ((value_end > value_start) &&
 1733             isspace(UCH(*(value_end - 1)))) {
 1734             value_end--;
 1735             while ((value_end > (value_start + 1)) &&
 1736                isspace(UCH(*value_end)) &&
 1737                isspace(UCH(*(value_end - 1)))) {
 1738             value_end--;
 1739             }
 1740         }
 1741         }
 1742     }
 1743 
 1744     /*
 1745      * Check for a separator character, and skip it.
 1746      */
 1747     if (*p == ';' || *p == ',')
 1748         p++;
 1749 
 1750     /*
 1751      * Now, we can handle this attribute/value pair.
 1752      */
 1753     if (attr_end > attr_start) {
 1754         char *value = alloc_attr_value(value_start, value_end);
 1755 
 1756         parse_flags = parse_attribute(parse_flags,
 1757                       cur_cookie,
 1758                       &cookie_len,
 1759                       attr_start,
 1760                       (int) (attr_end - attr_start),
 1761                       value,
 1762                       address,
 1763                       hostname,
 1764                       port);
 1765 
 1766         /*
 1767          * Presence of value is needed (indicated normally by '='),
 1768          * but it can be an empty string. - kw 1999-06-24
 1769          */
 1770         if (!(parse_flags & FLAGS_KNOWN_ATTR)
 1771         && value
 1772         && value_end >= value_start) {
 1773         /*
 1774          * If we've started a cookie, and it's not too big, save it in
 1775          * the CombinedCookies list.  - FM
 1776          */
 1777         if (cookie_len <= max_cookies_buffer
 1778             && cur_cookie != NULL) {
 1779             /*
 1780              * If we had a Set-Cookie2 header, make sure the version is
 1781              * at least 1, and mark it for quoting.  - FM
 1782              */
 1783             if (SetCookie2 != NULL) {
 1784             AssumeCookieVersion(cur_cookie);
 1785             cur_cookie->quoted = TRUE;
 1786             }
 1787             HTList_appendObject(CombinedCookies, cur_cookie);
 1788         } else if (cur_cookie != NULL) {
 1789             CTrace((tfp,
 1790                 "LYProcessSetCookies: Rejecting Set-Cookie: %s=%s\n",
 1791                 (cur_cookie->name ?
 1792                  cur_cookie->name : "[no name]"),
 1793                 (cur_cookie->value ?
 1794                  cur_cookie->value : "[no value]")));
 1795             CTrace((tfp,
 1796                 "                     due to excessive length!\n"));
 1797             freeCookie(cur_cookie);
 1798             cur_cookie = NULL;
 1799         }
 1800         /*
 1801          * Start a new cookie.  - FM
 1802          */
 1803         cur_cookie = newCookie();
 1804         NumCookies++;
 1805         cookie_len = 0;
 1806         MemAllocCopy(&(cur_cookie->name), attr_start, attr_end);
 1807         cookie_len += (int) strlen(cur_cookie->name);
 1808         MemAllocCopy(&(cur_cookie->value), value_start, value_end);
 1809         cookie_len += (int) strlen(cur_cookie->value);
 1810         StrAllocCopy(cur_cookie->domain, hostname);
 1811         cookie_len += (int) strlen(hostname);
 1812         StrAllocCopy(cur_cookie->path, path);
 1813         cookie_len += (cur_cookie->pathlen = (int) strlen(cur_cookie->path));
 1814         cur_cookie->port = port;
 1815         parse_flags = 0;
 1816         cur_cookie->quoted = Quoted;
 1817         Quoted = FALSE;
 1818         SetCookieDomain(cur_cookie, hostname);
 1819         }
 1820         FREE(value);
 1821     }
 1822     }
 1823 
 1824     /*
 1825      * Handle the final Set-Cookie cookie if within length limit.  - FM
 1826      */
 1827     if (NumCookies <= max_cookies_domain
 1828     && cookie_len <= max_cookies_buffer
 1829     && cur_cookie != NULL) {
 1830     if (SetCookie2 != NULL) {
 1831         AssumeCookieVersion(cur_cookie);
 1832         cur_cookie->quoted = TRUE;
 1833     }
 1834     HTList_appendObject(CombinedCookies, cur_cookie);
 1835     } else if (cur_cookie != NULL) {
 1836     CTrace((tfp, "LYProcessSetCookies: Rejecting Set-Cookie: %s=%s\n",
 1837         (cur_cookie->name ? cur_cookie->name : "[no name]"),
 1838         (cur_cookie->value ? cur_cookie->value : "[no value]")));
 1839     CTrace((tfp, "                     due to excessive %s%s%s\n",
 1840         (cookie_len > max_cookies_buffer ? "length" : ""),
 1841         (cookie_len > max_cookies_buffer && NumCookies > max_cookies_domain
 1842          ? " and "
 1843          : ""),
 1844         (NumCookies > max_cookies_domain ? "number!\n" : "!\n")));
 1845     freeCookie(cur_cookie);
 1846     cur_cookie = NULL;
 1847     }
 1848 
 1849     /*
 1850      * OK, now we can actually store any cookies in the CombinedCookies list.
 1851      * - FM
 1852      */
 1853     cl = CombinedCookies;
 1854     while (NULL != (co = (cookie *) HTList_nextObject(cl))) {
 1855     CTrace((tfp, "LYProcessSetCookie: attr=value pair: '%s=%s'\n",
 1856         (co->name ? co->name : "[no name]"),
 1857         (co->value ? co->value : "[no value]")));
 1858     if (co->expires > 0) {
 1859         CTrace((tfp, "                    expires: %" PRI_time_t ", %s\n",
 1860             CAST_time_t (co->expires),
 1861             ctime(&co->expires)));
 1862     }
 1863     if (isHTTPS_URL(address) &&
 1864         LYForceSSLCookiesSecure == TRUE &&
 1865         !(co->flags & COOKIE_FLAG_SECURE)) {
 1866         co->flags |= COOKIE_FLAG_SECURE;
 1867         CTrace((tfp, "                    Forced the 'secure' flag on.\n"));
 1868     }
 1869     store_cookie(co, hostname, path);
 1870     }
 1871     HTList_delete(CombinedCookies);
 1872     CombinedCookies = NULL;
 1873 
 1874     return;
 1875 }
 1876 
 1877 /*
 1878  *  Entry function for handling Set-Cookie: and/or Set-Cookie2:
 1879  *  reply headers.   They may have been concatenated as comma
 1880  *  separated lists in HTTP.c or HTMIME.c. - FM
 1881  */
 1882 void LYSetCookie(const char *SetCookie,
 1883          const char *SetCookie2,
 1884          const char *address)
 1885 {
 1886     BOOL BadHeaders = FALSE;
 1887     char *hostname = NULL, *path = NULL, *ptr;
 1888     int port = 80;
 1889 
 1890     /*
 1891      * Get the hostname, port and path of the address, and report the
 1892      * Set-Cookie and/or Set-Cookie2 header(s) if trace mode is on, but set the
 1893      * cookie(s) only if LYSetCookies is TRUE.  - FM
 1894      */
 1895     if (((hostname = HTParse(address, "", PARSE_HOST)) != NULL) &&
 1896     (ptr = StrChr(hostname, ':')) != NULL) {
 1897     /*
 1898      * Replace default port number.
 1899      */
 1900     *ptr = '\0';
 1901     ptr++;
 1902     port = atoi(ptr);
 1903     } else if (isHTTPS_URL(address)) {
 1904     port = 443;
 1905     }
 1906 
 1907     /*
 1908      * Get the path from the request URI.
 1909      */
 1910     if ((path = HTParse(address, "", PARSE_PATH | PARSE_PUNCTUATION)) != NULL) {
 1911     /*
 1912      * Trim off any parameters to provide something that we can compare
 1913      * against the cookie's path for verifying if it has the proper prefix.
 1914      */
 1915     if ((ptr = StrChr(path, '?')) != NULL) {
 1916         CTrace((tfp, "discarding params \"%s\" in request URI\n", ptr));
 1917         *ptr = '\0';
 1918     }
 1919     /* trim a trailing slash, unless we have only a "/" */
 1920     if ((ptr = strrchr(path, '/')) != NULL) {
 1921         if (ptr == path) {
 1922         ++ptr;
 1923         }
 1924         CTrace((tfp, "discarding \"%s\" from request URI\n", ptr));
 1925         *ptr = '\0';
 1926     }
 1927     }
 1928 
 1929     if (isEmpty(SetCookie) &&
 1930     isEmpty(SetCookie2)) {
 1931     /*
 1932      * Yuk, something must have gone wrong in HTMIME.c or HTTP.c because
 1933      * both SetCookie and SetCookie2 are NULL or zero-length.  - FM
 1934      */
 1935     BadHeaders = TRUE;
 1936     }
 1937     CTrace((tfp, "LYSetCookie called with host '%s', path '%s',\n",
 1938         NonNull(hostname),
 1939         NonNull(path)));
 1940     if (SetCookie) {
 1941     CTrace((tfp, "    and Set-Cookie: '%s'\n", SetCookie));
 1942     }
 1943     if (SetCookie2) {
 1944     CTrace((tfp, "    and Set-Cookie2: '%s'\n", SetCookie2));
 1945     }
 1946     if (LYSetCookies == FALSE || BadHeaders == TRUE) {
 1947     CTrace((tfp, "    Ignoring this Set-Cookie/Set-Cookie2 request.\n"));
 1948     }
 1949 
 1950     /*
 1951      * We're done if LYSetCookies is off or we have bad headers.  - FM
 1952      */
 1953     if (LYSetCookies == FALSE || BadHeaders == TRUE) {
 1954     FREE(hostname);
 1955     FREE(path);
 1956     return;
 1957     }
 1958 
 1959     /*
 1960      * Process the header(s).
 1961      */
 1962     LYProcessSetCookies(SetCookie, SetCookie2, address, hostname, path, port);
 1963     FREE(hostname);
 1964     FREE(path);
 1965     return;
 1966 }
 1967 
 1968 /*
 1969  *  Entry function from creating a Cookie: request header
 1970  *  if needed. - AK & FM
 1971  */
 1972 char *LYAddCookieHeader(char *hostname,
 1973             char *path,
 1974             int port,
 1975             int secure)
 1976 {
 1977     char *header = NULL;
 1978     HTList *hl = domain_list, *next = NULL;
 1979     domain_entry *de;
 1980 
 1981     CTrace((tfp, "LYCookie: Searching for '%s:%d', '%s'.\n",
 1982         NONNULL(hostname),
 1983         port,
 1984         NONNULL(path)));
 1985 
 1986     /*
 1987      * Search the cookie_list elements in the domain_list for any cookies
 1988      * associated with the //hostname:port/path
 1989      */
 1990     while (hl) {
 1991     de = (domain_entry *) hl->object;
 1992     next = hl->next;
 1993 
 1994     if (de != NULL) {
 1995         if (!HTList_isEmpty(de->cookie_list)) {
 1996         /*
 1997          * Scan the domain's cookie_list for any cookies we should
 1998          * include in our request header.
 1999          */
 2000         header = scan_cookie_sublist(hostname, path, port,
 2001                          de->cookie_list, header, secure);
 2002         } else if (de->bv == QUERY_USER && de->invcheck_bv == DEFAULT_INVCHECK_BV) {
 2003         /*
 2004          * No cookies in this domain, and no default accept/reject
 2005          * choice was set by the user, so delete the domain.  - FM
 2006          */
 2007         freeCookies(de);
 2008         HTList_removeObject(domain_list, de);
 2009         FREE(de);
 2010         }
 2011     }
 2012     hl = next;
 2013     }
 2014     if (header)
 2015     return (header);
 2016 
 2017     return (NULL);
 2018 }
 2019 
 2020 #ifdef USE_PERSISTENT_COOKIES
 2021 static int number_of_file_cookies = 0;
 2022 
 2023 /* rjp - cookie loading */
 2024 void LYLoadCookies(char *cookie_file)
 2025 {
 2026     FILE *cookie_handle;
 2027     char *buf = NULL;
 2028     static char domain[256], path[LY_MAXPATH], name[256], value[4100];
 2029     static char what[8], secure[8], expires_a[16];
 2030     /* *INDENT-OFF* */
 2031     static struct {
 2032     char *s;
 2033     size_t n;
 2034     } tok_values[] = {
 2035     { domain,   sizeof(domain) },
 2036     { what,     sizeof(what) },
 2037     { path,     sizeof(path) },
 2038     { secure,   sizeof(secure) },
 2039     { expires_a,    sizeof(expires_a) },
 2040     { name,     sizeof(name) },
 2041     { value,    sizeof(value) },
 2042     { NULL, 0 }
 2043     };
 2044     /* *INDENT-ON* */
 2045 
 2046     time_t expires;
 2047 
 2048     cookie_handle = fopen(cookie_file, TXT_R);
 2049     if (!cookie_handle)
 2050     return;
 2051 
 2052     CTrace((tfp, "LYLoadCookies: reading cookies from %s\n", cookie_file));
 2053 
 2054     number_of_file_cookies = 0;
 2055     while (LYSafeGets(&buf, cookie_handle) != 0) {
 2056     cookie *moo;
 2057     int tok_loop;
 2058     char *tok_out, *tok_ptr;
 2059 
 2060     LYTrimNewline(buf);
 2061     if (buf[0] == '\0' || buf[0] == '#') {
 2062         continue;
 2063     }
 2064 
 2065     number_of_file_cookies++;
 2066 
 2067     strcat(buf, "\t");  /* add sep after line if enough space - kw */
 2068 
 2069     /*
 2070      * Tokenise the cookie line into its component parts -
 2071      * this only works for Netscape style cookie files at the
 2072      * moment.  It may be worth investigating an alternative
 2073      * format for Lynx because the Netscape format isn't all
 2074      * that useful, or future-proof. - RP
 2075      *
 2076      * 'fixed' by using strsep instead of strtok.  No idea
 2077      * what kind of platform problems this might introduce. - RP
 2078      */
 2079     /*
 2080      * This fails when the path is blank
 2081      *
 2082      * sscanf(buf, "%s\t%s\t%s\t%s\t%d\t%s\t%[ -~]",
 2083      *  domain, what, path, secure, &expires, name, value);
 2084      */
 2085     CTrace((tfp, "LYLoadCookies: tokenising %s\n", buf));
 2086     tok_ptr = buf;
 2087     tok_out = LYstrsep(&tok_ptr, "\t");
 2088     for (tok_loop = 0; tok_out && tok_values[tok_loop].s; tok_loop++) {
 2089         CTrace((tfp, "\t%d:[%03d]:[%s]\n",
 2090             tok_loop, (int) (tok_out - buf), tok_out));
 2091         LYStrNCpy(tok_values[tok_loop].s,
 2092               tok_out,
 2093               (int) tok_values[tok_loop].n);
 2094         /*
 2095          * It looks like strtok ignores a leading delimiter,
 2096          * which makes things a bit more interesting.  Something
 2097          * like "FALSE\t\tFALSE\t" translates to FALSE,FALSE
 2098          * instead of FALSE,,FALSE. - RP
 2099          */
 2100         tok_out = LYstrsep(&tok_ptr, "\t");
 2101     }
 2102 
 2103     if (tok_values[tok_loop].s) {
 2104         /* tok_out in above loop must have been NULL prematurely - kw */
 2105         CTrace((tfp,
 2106             "*** wrong format: not enough tokens, ignoring line!\n"));
 2107         continue;
 2108     }
 2109 
 2110     expires = atol(expires_a);
 2111     CTrace((tfp, "expires:\t%s\n", ctime(&expires)));
 2112     moo = newCookie();
 2113     StrAllocCopy(moo->domain, domain);
 2114     SetCookieDomain(moo, domain);
 2115     StrAllocCopy(moo->path, path);
 2116     StrAllocCopy(moo->name, name);
 2117     if (value[0] == '"' &&
 2118         value[1] && value[strlen(value) - 1] == '"' &&
 2119         value[strlen(value) - 2] != '\\') {
 2120         value[strlen(value) - 1] = '\0';
 2121         StrAllocCopy(moo->value, value + 1);
 2122         moo->quoted = TRUE;
 2123     } else {
 2124         StrAllocCopy(moo->value, value);
 2125     }
 2126     moo->pathlen = (int) strlen(moo->path);
 2127     /*
 2128      *  Justification for following flags:
 2129      *  COOKIE_FLAG_FROM_FILE    So we know were it comes from.
 2130      *  COOKIE_FLAG_EXPIRES_SET  It must have had an explicit
 2131      *                           expiration originally, otherwise
 2132      *                           it would not be in the file.
 2133      *  COOKIE_FLAG_DOMAIN_SET,  We don't know whether these were
 2134      *   COOKIE_FLAG_PATH_SET    explicit or implicit, but this
 2135      *                           only matters for sending version 1
 2136      *                           cookies; the cookies read from the
 2137      *                           file are currently treated all like
 2138      *                           version 0 (we don't set moo->version)
 2139      *                           so $Domain= and $Path= will normally
 2140      *                           not be sent to the server.  But if
 2141      *                           these cookies somehow get mixed with
 2142      *                           new version 1 cookies we may end up
 2143      *                           sending version 1 to the server, and
 2144      *                           in that case we should send $Domain
 2145      *                           and $Path.  The state-man-mec drafts
 2146      *                           and RFC 2109 say that $Domain and
 2147      *                           $Path SHOULD be omitted if they were
 2148      *                           not given explicitly, but not that
 2149      *                           they MUST be omitted.
 2150      *                           See 8.2 Cookie Spoofing in draft -10
 2151      *                           for a good reason to send them.
 2152      *                           However, an explicit domain should be
 2153      *                           now prefixed with a dot (unless it is
 2154      *                           for a single host), so we check for
 2155      *                           that.
 2156      *  COOKIE_FLAG_SECURE       Should have "FALSE" for normal,
 2157      *                           otherwise set it.
 2158      */
 2159     moo->flags |= COOKIE_FLAG_FROM_FILE | COOKIE_FLAG_EXPIRES_SET |
 2160         COOKIE_FLAG_PATH_SET;
 2161     if (LeadingDot(domain))
 2162         moo->flags |= COOKIE_FLAG_DOMAIN_SET;
 2163     if (secure[0] != 'F')
 2164         moo->flags |= COOKIE_FLAG_SECURE;
 2165     /* @@@ Should we set port to 443 if secure is set? @@@ */
 2166     moo->expires = expires;
 2167     /*
 2168      * I don't like using this to store the cookies because it's
 2169      * designed to store cookies that have been received from an
 2170      * HTTP request, not from a persistent cookie jar.  Hence the
 2171      * mucking about with the COOKIE_FLAG_FROM_FILE above. - RP
 2172      */
 2173     store_cookie(moo, domain, path);
 2174     }
 2175     LYCloseInput(cookie_handle);
 2176 }
 2177 
 2178 static FILE *NewCookieFile(char *cookie_file)
 2179 {
 2180     CTrace((tfp, "LYStoreCookies: save cookies to %s on exit\n", cookie_file));
 2181     return LYNewTxtFile(cookie_file);
 2182 }
 2183 
 2184 /* rjp - persistent cookie support */
 2185 void LYStoreCookies(char *cookie_file)
 2186 {
 2187     HTList *dl, *cl;
 2188     domain_entry *de;
 2189     cookie *co;
 2190     FILE *cookie_handle = NULL;
 2191     time_t now = time(NULL);    /* system specific? - RP */
 2192 
 2193     if (isEmpty(cookie_file) || !strcmp(cookie_file, "/dev/null")) {
 2194     /* We give /dev/null the Unix meaning, regardless of OS */
 2195     return;
 2196     }
 2197 
 2198     /*
 2199      * Check whether we have something to do.  - FM
 2200      */
 2201     if (HTList_isEmpty(domain_list) &&
 2202     number_of_file_cookies == 0) {
 2203     /* No cookies now, and haven't read any,
 2204      * so don't bother updating the file.
 2205      */
 2206     return;
 2207     }
 2208 
 2209     /* if we read cookies from the file, we'll update it even if now empty */
 2210     if (number_of_file_cookies != 0) {
 2211     cookie_handle = NewCookieFile(cookie_file);
 2212     if (cookie_handle == NULL)
 2213         return;
 2214     }
 2215 
 2216     for (dl = domain_list; dl != NULL; dl = dl->next) {
 2217     de = (domain_entry *) (dl->object);
 2218     if (de == NULL)
 2219         /*
 2220          * Fote says the first object is NULL.  Go with that.
 2221          */
 2222         continue;
 2223 
 2224     /*
 2225      * Show the domain's cookies.  - FM
 2226      */
 2227     for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
 2228         /*
 2229          * First object is always NULL.  - FM
 2230          */
 2231         if ((co = (cookie *) cl->object) == NULL)
 2232         continue;
 2233 
 2234         CTrace((tfp, "LYStoreCookies: %" PRI_time_t " %s %" PRI_time_t " ",
 2235             CAST_time_t (now),
 2236             (now < co->expires) ? "<" : ">",
 2237             CAST_time_t (co->expires)));
 2238 
 2239         if ((co->flags & COOKIE_FLAG_DISCARD)) {
 2240         CTrace((tfp, "not stored - DISCARD\n"));
 2241         continue;
 2242         } else if (!(co->flags & COOKIE_FLAG_EXPIRES_SET)) {
 2243         CTrace((tfp, "not stored - no expiration time\n"));
 2244         continue;
 2245         } else if (co->expires <= now) {
 2246         CTrace((tfp, "not stored - EXPIRED\n"));
 2247         continue;
 2248         }
 2249 
 2250         /* when we're sure we'll write to the file - open it */
 2251         if (cookie_handle == NULL) {
 2252         cookie_handle = NewCookieFile(cookie_file);
 2253         if (cookie_handle == NULL)
 2254             return;
 2255         }
 2256 
 2257         fprintf(cookie_handle, "%s\t%s\t%s\t%s\t%" PRI_time_t
 2258             "\t%s\t%s%s%s\n",
 2259             de->ddomain,
 2260             (co->flags & COOKIE_FLAG_DOMAIN_SET) ? "TRUE" : "FALSE",
 2261             co->path,
 2262             (co->flags & COOKIE_FLAG_SECURE) ? "TRUE" : "FALSE",
 2263             CAST_time_t (co->expires), co->name,
 2264             (co->quoted ? "\"" : ""),
 2265             NonNull(co->value),
 2266             (co->quoted ? "\"" : ""));
 2267 
 2268         CTrace((tfp, "STORED %s\n", de->ddomain));
 2269     }
 2270     }
 2271     if (cookie_handle != NULL) {
 2272     LYCloseOutput(cookie_handle);
 2273     HTSYS_purge(cookie_file);
 2274     }
 2275 }
 2276 #endif
 2277 
 2278 /*
 2279  * Check if the given string is completely US-ASCII.  If so (and if the
 2280  * original were hex-encoded), it is likely to be more useful in a decoded
 2281  * form.
 2282  */
 2283 static BOOLEAN valueNonAscii(const char *value)
 2284 {
 2285     BOOLEAN result = FALSE;
 2286 
 2287     while (*value != '\0') {
 2288     int ch = UCH(*value++);
 2289 
 2290     if (ch < 32 || ch > 126) {
 2291         result = TRUE;
 2292         break;
 2293     }
 2294     }
 2295 
 2296     return result;
 2297 }
 2298 
 2299 /*  LYHandleCookies - F.Macrides (macrides@sci.wfeb.edu)
 2300  *  ---------------
 2301  *
 2302  *  Lists all cookies by domain, and allows deletions of
 2303  *  individual cookies or entire domains, and changes of
 2304  *  'allow' settings.  The list is invoked via the COOKIE_JAR
 2305  *  command (Ctrl-K), and deletions or changes of 'allow'
 2306  *  settings are done by activating links in that list.
 2307  *  The procedure uses a LYNXCOOKIE: internal URL scheme.
 2308  *
 2309  *  Semantics:
 2310  *  LYNXCOOKIE:/            Create and load the Cookie Jar Page.
 2311  *  LYNXCOOKIE://domain     Manipulate the domain.
 2312  *  LYNXCOOKIE://domain/lynxID  Delete cookie with lynxID in domain.
 2313  *
 2314  *  New functions can be added as extensions to the path, and/or by
 2315  *  assigning meanings to ;parameters, a ?searchpart, and/or #fragments.
 2316  */
 2317 static int LYHandleCookies(const char *arg,
 2318                HTParentAnchor *anAnchor,
 2319                HTFormat format_out,
 2320                HTStream *sink)
 2321 {
 2322     HTFormat format_in = WWW_HTML;
 2323     HTStream *target = NULL;
 2324     char *buf = NULL;
 2325     char *domain = NULL;
 2326     char *lynxID = NULL;
 2327     HTList *dl, *cl, *next;
 2328     domain_entry *de;
 2329     cookie *co;
 2330     char *name = NULL, *value = NULL, *path = NULL;
 2331     char *comment = NULL, *Address = NULL, *Title = NULL;
 2332     int ch;
 2333 
 2334     /*
 2335      * Check whether we have something to do.  - FM
 2336      */
 2337     if (HTList_isEmpty(domain_list)) {
 2338     HTProgress(COOKIE_JAR_IS_EMPTY);
 2339     LYSleepMsg();
 2340     HTNoDataOK = 1;
 2341     return (HT_NO_DATA);
 2342     }
 2343 
 2344     /*
 2345      * If there's a domain string in the "host" field of the LYNXCOOKIE:  URL,
 2346      * this is a request to delete something or change and 'allow' setting.  -
 2347      * FM
 2348      */
 2349     if ((domain = HTParse(arg, "", PARSE_HOST)) != NULL) {
 2350     if (*domain == '\0') {
 2351         FREE(domain);
 2352     } else {
 2353         /*
 2354          * If there is a path string (not just a slash) in the LYNXCOOKIE:
 2355          * URL, that's a cookie's lynxID and this is a request to delete it
 2356          * from the Cookie Jar.  - FM
 2357          */
 2358         if ((lynxID = HTParse(arg, "", PARSE_PATH)) != NULL) {
 2359         if (*lynxID == '\0') {
 2360             FREE(lynxID);
 2361         }
 2362         }
 2363     }
 2364     }
 2365     if (domain) {
 2366     /*
 2367      * Seek the domain in the domain_list structure.  - FM
 2368      */
 2369     if ((de = find_domain_entry(domain)) != NULL) {
 2370         FREE(domain);
 2371         /*
 2372          * We found the domain.  Check whether a lynxID is present.  - FM
 2373          */
 2374         if (lynxID) {
 2375         /*
 2376          * Seek and delete the cookie with this lynxID in the domain's
 2377          * cookie list.  - FM
 2378          */
 2379         for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
 2380             if ((co = (cookie *) cl->object) == NULL)
 2381             /*
 2382              * First object is always empty.  - FM
 2383              */
 2384             continue;
 2385             if (!strcmp(lynxID, co->lynxID)) {
 2386             /*
 2387              * We found the cookie.  Delete it if confirmed.  - FM
 2388              */
 2389             if (HTConfirm(DELETE_COOKIE_CONFIRMATION) == FALSE) {
 2390                 FREE(lynxID);
 2391                 HTNoDataOK = 1;
 2392                 return (HT_NO_DATA);
 2393             }
 2394             HTList_removeObject(de->cookie_list, co);
 2395             freeCookie(co);
 2396             co = NULL;
 2397             total_cookies--;
 2398             if ((de->bv == QUERY_USER &&
 2399                  HTList_isEmpty(de->cookie_list)) &&
 2400                 HTConfirm(DELETE_EMPTY_DOMAIN_CONFIRMATION)) {
 2401                 /*
 2402                  * No more cookies in this domain, no default
 2403                  * accept/reject choice was set by the user, and
 2404                  * got confirmation on deleting the domain, so do
 2405                  * it.  - FM
 2406                  */
 2407                 freeCookies(de);
 2408                 HTList_removeObject(domain_list, de);
 2409                 FREE(de);
 2410                 HTProgress(DOMAIN_EATEN);
 2411             } else {
 2412                 HTProgress(COOKIE_EATEN);
 2413             }
 2414             LYSleepMsg();
 2415             HTNoDataOK = 1;
 2416             break;
 2417             }
 2418         }
 2419         } else {
 2420         /*
 2421          * Prompt whether to delete all of the cookies in this domain,
 2422          * or the domain if no cookies in it, or to change its 'allow'
 2423          * setting, or to cancel, and then act on the user's response.
 2424          * - FM
 2425          */
 2426         if (HTList_isEmpty(de->cookie_list)) {
 2427             _statusline(DELETE_DOMAIN_SET_ALLOW_OR_CANCEL);
 2428         } else {
 2429             _statusline(DELETE_COOKIES_SET_ALLOW_OR_CANCEL);
 2430         }
 2431         HTNoDataOK = 1;
 2432         while (1) {
 2433             ch = LYgetch_single();
 2434 #ifdef VMS
 2435             if (HadVMSInterrupt) {
 2436             HadVMSInterrupt = FALSE;
 2437             ch = 'C';
 2438             }
 2439 #endif /* VMS */
 2440             switch (ch) {
 2441             case 'A':
 2442             /*
 2443              * Set to accept all cookies from this domain.  - FM
 2444              */
 2445             de->bv = ACCEPT_ALWAYS;
 2446             HTUserMsg2(ALWAYS_ALLOWING_COOKIES, de->ddomain);
 2447             return (HT_NO_DATA);
 2448 
 2449             case 'C':
 2450             /*
 2451              * Cancelled.  - FM
 2452              */
 2453               reject:
 2454             HTUserMsg(CANCELLED);
 2455             return (HT_NO_DATA);
 2456 
 2457             case 'D':
 2458             if (HTList_isEmpty(de->cookie_list)) {
 2459                 /*
 2460                  * We had an empty domain, so we were asked to
 2461                  * delete it.  - FM
 2462                  */
 2463                 freeCookies(de);
 2464                 HTList_removeObject(domain_list, de);
 2465                 FREE(de);
 2466                 HTProgress(DOMAIN_EATEN);
 2467                 LYSleepMsg();
 2468                 break;
 2469             }
 2470               Delete_all_cookies_in_domain:
 2471             /*
 2472              * Delete all cookies in this domain.  - FM
 2473              */
 2474             cl = de->cookie_list;
 2475             while (cl) {
 2476                 next = cl->next;
 2477                 co = (cookie *) (cl->object);
 2478                 if (co) {
 2479                 HTList_removeObject(de->cookie_list, co);
 2480                 freeCookie(co);
 2481                 co = NULL;
 2482                 total_cookies--;
 2483                 }
 2484                 cl = next;
 2485             }
 2486             HTProgress(DOMAIN_COOKIES_EATEN);
 2487             LYSleepMsg();
 2488             /*
 2489              * If a default accept/reject choice is set, we're
 2490              * done.  - FM
 2491              */
 2492             if (de->bv != QUERY_USER)
 2493                 return (HT_NO_DATA);
 2494             /*
 2495              * Check whether to delete the empty domain.  - FM
 2496              */
 2497             if (HTConfirm(DELETE_EMPTY_DOMAIN_CONFIRMATION)) {
 2498                 freeCookies(de);
 2499                 HTList_removeObject(domain_list, de);
 2500                 FREE(de);
 2501                 HTProgress(DOMAIN_EATEN);
 2502                 LYSleepMsg();
 2503             }
 2504             break;
 2505 
 2506             case 'P':
 2507             /*
 2508              * Set to prompt for cookie acceptance from this
 2509              * domain.  - FM
 2510              */
 2511             de->bv = QUERY_USER;
 2512             HTUserMsg2(PROMPTING_TO_ALLOW_COOKIES, de->ddomain);
 2513             return (HT_NO_DATA);
 2514 
 2515             case 'V':
 2516             /*
 2517              * Set to reject all cookies from this domain.  - FM
 2518              */
 2519             de->bv = REJECT_ALWAYS;
 2520             HTUserMsg2(NEVER_ALLOWING_COOKIES, de->ddomain);
 2521             if ((!HTList_isEmpty(de->cookie_list)) &&
 2522                 HTConfirm(DELETE_ALL_COOKIES_IN_DOMAIN))
 2523                 goto Delete_all_cookies_in_domain;
 2524             return (HT_NO_DATA);
 2525 
 2526             default:
 2527             if (LYCharIsINTERRUPT(ch))
 2528                 goto reject;
 2529             continue;
 2530             }
 2531             break;
 2532         }
 2533         }
 2534     }
 2535     if (HTList_isEmpty(domain_list)) {
 2536         /*
 2537          * There are no more domains left.  Don't delete the domain_list,
 2538          * otherwise atexit may be called multiple times.  - kw
 2539          */
 2540         HTProgress(ALL_COOKIES_EATEN);
 2541         LYSleepMsg();
 2542     }
 2543     FREE(domain);
 2544     FREE(lynxID);
 2545     return (HT_NO_DATA);
 2546     }
 2547 
 2548     /*
 2549      * If we get to here, it was a LYNXCOOKIE:/ URL for creating and displaying
 2550      * the Cookie Jar Page, or we didn't find the domain or cookie in a
 2551      * deletion request.  Set up an HTML stream and return an updated Cookie
 2552      * Jar Page.  - FM
 2553      */
 2554     target = HTStreamStack(format_in,
 2555                format_out,
 2556                sink, anAnchor);
 2557     if (target == NULL) {
 2558     HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O,
 2559            HTAtom_name(format_in), HTAtom_name(format_out));
 2560     HTAlert(buf);
 2561     FREE(buf);
 2562     return (HT_NOT_LOADED);
 2563     }
 2564 
 2565     /*
 2566      * Load HTML strings into buf and pass buf to the target for parsing and
 2567      * rendering.  - FM
 2568      */
 2569 #define PUTS(buf)    (*target->isa->put_block)(target, buf, (int) strlen(buf))
 2570 
 2571     WriteStreamTitle(target, COOKIE_JAR_TITLE);
 2572     HTSprintf0(&buf, "<h1>%s (%s)%s<a href=\"%s%s\">%s</a></h1>\n",
 2573            LYNX_NAME, LYNX_VERSION,
 2574            HELP_ON_SEGMENT,
 2575            helpfilepath, COOKIE_JAR_HELP, COOKIE_JAR_TITLE);
 2576     PUTS(buf);
 2577 
 2578     HTSprintf0(&buf, "<div><em>Note:</em> %s\n", ACTIVATE_TO_GOBBLE);
 2579     PUTS(buf);
 2580     HTSprintf0(&buf, "%s</div>\n", OR_CHANGE_ALLOW);
 2581     PUTS(buf);
 2582 
 2583     HTSprintf0(&buf, "<dl compact>\n");
 2584     PUTS(buf);
 2585     for (dl = domain_list; dl != NULL; dl = dl->next) {
 2586     de = (domain_entry *) (dl->object);
 2587     if (de == NULL)
 2588         /*
 2589          * First object always is NULL.  - FM
 2590          */
 2591         continue;
 2592 
 2593     /*
 2594      * Show the domain link and 'allow' setting.  - FM
 2595      */
 2596     HTSprintf0(&buf,
 2597            "<dt>%s<dd><a href=\"%s//%s/\"><em>Domain:</em> %s</a>\n",
 2598            de->ddomain, STR_LYNXCOOKIE, de->ddomain, de->ddomain);
 2599     PUTS(buf);
 2600     switch (de->bv) {
 2601     case (ACCEPT_ALWAYS):
 2602         HTSprintf0(&buf, COOKIES_ALWAYS_ALLOWED);
 2603         break;
 2604     case (REJECT_ALWAYS):
 2605         HTSprintf0(&buf, COOKIES_NEVER_ALLOWED);
 2606         break;
 2607     case (QUERY_USER):
 2608         HTSprintf0(&buf, COOKIES_ALLOWED_VIA_PROMPT);
 2609         break;
 2610     }
 2611     PUTS(buf);
 2612     HTSprintf0(&buf, "\n");
 2613     PUTS(buf);
 2614 
 2615     /*
 2616      * Show the domain's cookies.  - FM
 2617      */
 2618     for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
 2619         if ((co = (cookie *) cl->object) == NULL)
 2620         /*
 2621          * First object is always NULL.  - FM
 2622          */
 2623         continue;
 2624 
 2625         /*
 2626          * Show the name=value pair.  - FM
 2627          */
 2628         if (co->name) {
 2629         StrAllocCopy(name, co->name);
 2630         LYEntify(&name, TRUE);
 2631         } else {
 2632         StrAllocCopy(name, NO_NAME);
 2633         }
 2634         if (co->value) {
 2635         StrAllocCopy(value, co->value);
 2636         HTUnEscape(value);
 2637         if (valueNonAscii(value))
 2638             strcpy(value, co->value);
 2639         LYEntify(&value, TRUE);
 2640         } else {
 2641         StrAllocCopy(value, NO_VALUE);
 2642         }
 2643         HTSprintf0(&buf, "<dd><a href=\"%s//%s/%s\"><em>%s</em>=%s</a>\n",
 2644                STR_LYNXCOOKIE, de->ddomain, co->lynxID, name, value);
 2645         FREE(name);
 2646         FREE(value);
 2647         PUTS(buf);
 2648 
 2649         if (co->flags & COOKIE_FLAG_FROM_FILE) {
 2650         HTSprintf0(&buf, "%s\n",
 2651                gettext("(from a previous session)"));
 2652         PUTS(buf);
 2653         }
 2654 
 2655         /*
 2656          * Show the path, port, secure and discard setting.  - FM
 2657          */
 2658         if (co->path) {
 2659         StrAllocCopy(path, co->path);
 2660         LYEntify(&path, TRUE);
 2661         } else {
 2662         StrAllocCopy(path, "/");
 2663         }
 2664         HTSprintf0(&buf,
 2665                "<dd><em>Path:</em> %s\n<dd><em>Port:</em> %d <em>Secure:</em> %s <em>Discard:</em> %s\n",
 2666                path, co->port,
 2667                ((co->flags & COOKIE_FLAG_SECURE) ? "YES" : "NO"),
 2668                ((co->flags & COOKIE_FLAG_DISCARD) ? "YES" : "NO"));
 2669         FREE(path);
 2670         PUTS(buf);
 2671 
 2672         /*
 2673          * Show the list of acceptable ports, if present.  - FM
 2674          */
 2675         if (co->PortList) {
 2676         HTSprintf0(&buf, "<dd><em>PortList:</em> \"%s\"\n", co->PortList);
 2677         PUTS(buf);
 2678         }
 2679 
 2680         /*
 2681          * Show the commentURL, if we have one.  - FM
 2682          */
 2683         if (co->commentURL) {
 2684         StrAllocCopy(Address, co->commentURL);
 2685         LYEntify(&Address, FALSE);
 2686         StrAllocCopy(Title, co->commentURL);
 2687         LYEntify(&Title, TRUE);
 2688         HTSprintf0(&buf,
 2689                "<dd><em>CommentURL:</em> <a href=\"%s\">%s</a>\n",
 2690                Address,
 2691                Title);
 2692         FREE(Address);
 2693         FREE(Title);
 2694         PUTS(buf);
 2695         }
 2696 
 2697         /*
 2698          * Show the comment, if we have one.  - FM
 2699          */
 2700         if (co->comment) {
 2701         StrAllocCopy(comment, co->comment);
 2702         LYEntify(&comment, TRUE);
 2703         HTSprintf0(&buf, "<dd><em>Comment:</em> %s\n", comment);
 2704         FREE(comment);
 2705         PUTS(buf);
 2706         }
 2707 
 2708         /*
 2709          * Show the Maximum Gobble Date.  - FM
 2710          */
 2711         HTSprintf0(&buf, "<dd><em>%s</em> %s%s",
 2712                gettext("Maximum Gobble Date:"),
 2713                ((co->flags & COOKIE_FLAG_EXPIRES_SET)
 2714             ?
 2715             ctime(&co->expires) : END_OF_SESSION),
 2716                ((co->flags & COOKIE_FLAG_EXPIRES_SET)
 2717             ?
 2718             "" : "\n"));
 2719         PUTS(buf);
 2720     }
 2721     HTSprintf0(&buf, "\n");
 2722     PUTS(buf);
 2723     }
 2724     HTSprintf0(&buf, "</dl>\n</body>\n</html>\n");
 2725     PUTS(buf);
 2726 
 2727     /*
 2728      * Free the target to complete loading of the Cookie Jar Page, and report a
 2729      * successful load.  - FM
 2730      */
 2731     (*target->isa->_free) (target);
 2732     FREE(buf);
 2733     return (HT_LOADED);
 2734 }
 2735 
 2736 /*      cookie_domain_flag_set
 2737  *      ----------------------
 2738  *      All purpose function to handle setting domain flags for a
 2739  *      comma-delimited list of domains.  cookie_domain_flags handles
 2740  *      invcheck behavior, as well as accept/reject behavior. - BJP
 2741  */
 2742 static void cookie_domain_flag_set(char *domainstr,
 2743                    int flag)
 2744 {
 2745     domain_entry *de = NULL;
 2746     char **str = typecalloc(char *);
 2747     char *dstr = NULL;
 2748     char *strsmall = NULL;
 2749 
 2750     if (str == NULL) {
 2751     HTAlwaysAlert(gettext("Internal"),
 2752               gettext("cookie_domain_flag_set error, aborting program"));
 2753     exit_immediately(EXIT_FAILURE);
 2754     }
 2755 
 2756     /*
 2757      * Is this the first domain we're handling?  If so, initialize domain_list.
 2758      */
 2759     if (domain_list == NULL) {
 2760 #ifdef LY_FIND_LEAKS
 2761     atexit(LYCookieJar_free);
 2762 #endif
 2763     domain_list = HTList_new();
 2764     total_cookies = 0;
 2765     }
 2766 
 2767     StrAllocCopy(dstr, domainstr);
 2768 
 2769     *str = dstr;
 2770 
 2771     while ((strsmall = LYstrsep(str, ",")) != 0) {
 2772 
 2773     if (*strsmall == '\0')
 2774         /* Never add a domain for empty string.  It would actually
 2775          * make more sense to use strtok here. - kw */
 2776         continue;
 2777 
 2778     /*
 2779      * Check the list of existing domains to see if this is a
 2780      * re-setting of an already existing domain -- if so, just
 2781      * change the behavior, if not, create a new domain entry.
 2782      */
 2783 
 2784     if ((de = find_domain_entry(strsmall)) == NULL) {
 2785         de = typecalloc(domain_entry);
 2786         if (de == NULL)
 2787         outofmem(__FILE__, "cookie_domain_flag_set");
 2788 
 2789         de->bv = ACCEPT_ALWAYS;
 2790         de->invcheck_bv = INVCHECK_QUERY;
 2791 
 2792         switch (flag) {
 2793         case (FLAG_ACCEPT_ALWAYS):
 2794         de->invcheck_bv = DEFAULT_INVCHECK_BV;
 2795         break;
 2796         case (FLAG_REJECT_ALWAYS):
 2797         de->invcheck_bv = DEFAULT_INVCHECK_BV;
 2798         break;
 2799         case (FLAG_QUERY_USER):
 2800         de->invcheck_bv = DEFAULT_INVCHECK_BV;
 2801         break;
 2802         case (FLAG_INVCHECK_QUERY):
 2803         de->bv = QUERY_USER;
 2804         break;
 2805         case (FLAG_INVCHECK_STRICT):
 2806         de->bv = QUERY_USER;
 2807         break;
 2808         case (FLAG_INVCHECK_LOOSE):
 2809         de->bv = QUERY_USER;
 2810         break;
 2811         }
 2812 
 2813         StrAllocCopy(de->domain, strsmall);
 2814         StrAllocCopy(de->ddomain, SkipLeadingDot(strsmall));
 2815         de->cookie_list = HTList_new();
 2816         HTList_appendObject(domain_list, de);
 2817     }
 2818     switch (flag) {
 2819     case (FLAG_ACCEPT_ALWAYS):
 2820         de->bv = ACCEPT_ALWAYS;
 2821         break;
 2822     case (FLAG_REJECT_ALWAYS):
 2823         de->bv = REJECT_ALWAYS;
 2824         break;
 2825     case (FLAG_QUERY_USER):
 2826         de->bv = QUERY_USER;
 2827         break;
 2828     case (FLAG_INVCHECK_QUERY):
 2829         de->invcheck_bv = INVCHECK_QUERY;
 2830         break;
 2831     case (FLAG_INVCHECK_STRICT):
 2832         de->invcheck_bv = INVCHECK_STRICT;
 2833         break;
 2834     case (FLAG_INVCHECK_LOOSE):
 2835         de->invcheck_bv = INVCHECK_LOOSE;
 2836         break;
 2837     }
 2838     CTrace((tfp,
 2839         "cookie_domain_flag_set (%s, bv=%u, invcheck_bv=%u)\n",
 2840         strsmall, de->bv, de->invcheck_bv));
 2841     }
 2842 
 2843     FREE(strsmall);
 2844     FREE(str);
 2845     FREE(dstr);
 2846 }
 2847 
 2848 /*
 2849  * If any COOKIE_{ACCEPT,REJECT}_DOMAINS have been defined, process them.
 2850  * These are comma delimited lists of domains.  - BJP
 2851  *
 2852  * And for query/strict/loose invalid cookie checking.  - BJP
 2853  */
 2854 void LYConfigCookies(void)
 2855 {
 2856     static const struct {
 2857     char **domain;
 2858     int flag;
 2859     int once;
 2860     } table[] = {
 2861     /* *INDENT-OFF* */
 2862     { &LYCookieSAcceptDomains,  FLAG_ACCEPT_ALWAYS,   TRUE },
 2863     { &LYCookieSRejectDomains,  FLAG_REJECT_ALWAYS,   TRUE },
 2864     { &LYCookieSStrictCheckDomains, FLAG_INVCHECK_STRICT, TRUE },
 2865     { &LYCookieSLooseCheckDomains,  FLAG_INVCHECK_LOOSE,  TRUE },
 2866     { &LYCookieSQueryCheckDomains,  FLAG_INVCHECK_QUERY,  TRUE },
 2867     { &LYCookieAcceptDomains,   FLAG_ACCEPT_ALWAYS,   FALSE },
 2868     { &LYCookieRejectDomains,   FLAG_REJECT_ALWAYS,   FALSE },
 2869     { &LYCookieStrictCheckDomains,  FLAG_INVCHECK_STRICT, FALSE },
 2870     { &LYCookieLooseCheckDomains,   FLAG_INVCHECK_LOOSE,  FALSE },
 2871     { &LYCookieQueryCheckDomains,   FLAG_INVCHECK_QUERY,  FALSE },
 2872     /* *INDENT-ON* */
 2873 
 2874     };
 2875     unsigned n;
 2876 
 2877     CTrace((tfp, "LYConfigCookies\n"));
 2878     for (n = 0; n < TABLESIZE(table); n++) {
 2879     if (*(table[n].domain) != NULL) {
 2880         cookie_domain_flag_set(*(table[n].domain), table[n].flag);
 2881         /*
 2882          * Discard the value for system settings after we've used them.
 2883          * The local settings will be merged with the contents of .lynxrc
 2884          */
 2885         if (table[n].once) {
 2886         FREE(*(table[n].domain));
 2887         }
 2888     }
 2889     }
 2890 }
 2891 
 2892 #ifdef GLOBALDEF_IS_MACRO
 2893 #define _LYCOOKIE_C_GLOBALDEF_1_INIT { "LYNXCOOKIE",LYHandleCookies,0}
 2894 GLOBALDEF(HTProtocol, LYLynxCookies, _LYCOOKIE_C_GLOBALDEF_1_INIT);
 2895 #else
 2896 GLOBALDEF HTProtocol LYLynxCookies =
 2897 {"LYNXCOOKIE", LYHandleCookies, 0};
 2898 #endif /* GLOBALDEF_IS_MACRO */