diff options
author | Jorge Arellano Cid <jcid@dillo.org> | 2010-01-18 08:55:05 -0300 |
---|---|---|
committer | Jorge Arellano Cid <jcid@dillo.org> | 2010-01-18 08:55:05 -0300 |
commit | a593fe8b62e148da741e8ce054f4b3bedcf81403 (patch) | |
tree | 42c11938246fb6b989c1df0f36794f3759fbb9b7 | |
parent | 6c118637ea99f74c49803ac528d70a38c54bbd64 (diff) | |
parent | aa10e22a8530fcd4e4b18540a73ffc22c8d4610e (diff) |
merge
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | configure.in | 1 | ||||
-rw-r--r-- | dillorc | 3 | ||||
-rw-r--r-- | doc/Cookies.txt | 6 | ||||
-rw-r--r-- | dpi/cookies.c | 1072 | ||||
-rw-r--r-- | dpid/dpid.c | 6 | ||||
-rw-r--r-- | dw/style.cc | 2 | ||||
-rw-r--r-- | dw/style.hh | 4 | ||||
-rw-r--r-- | dw/table.hh | 24 | ||||
-rw-r--r-- | dw/textblock.cc | 4 | ||||
-rw-r--r-- | src/IO/Makefile.am | 1 | ||||
-rw-r--r-- | src/IO/dpi.c | 21 | ||||
-rw-r--r-- | src/cache.c | 11 | ||||
-rw-r--r-- | src/cookies.c | 42 | ||||
-rw-r--r-- | src/cookies.h | 3 | ||||
-rw-r--r-- | src/css.cc | 8 | ||||
-rw-r--r-- | src/doctree.hh | 48 | ||||
-rw-r--r-- | src/styleengine.cc | 61 | ||||
-rw-r--r-- | src/styleengine.hh | 20 |
19 files changed, 649 insertions, 691 deletions
@@ -16,6 +16,7 @@ dillo-2.2 [??] - Add support for CSS property list-style-position. - Support border-width: thin | medium | thick. - Fix CSS_SHORTHAND_DIRECTIONS case in CssParser. + - Add quirk to reset font properties in tables (fixes e.g. gmail). Patch: Johannes Hofmann +- Cleaned up system includes in dpid directory. - Fixed CustProgressBox() for systems without weak symbols. @@ -56,6 +57,8 @@ dillo-2.2 [??] - Enable popup menu below bottom of page content (BUG#856). - Handle JPEGs with CMYK color space. - Allow keysyms in keysrc. + - Explicitly check installation bindir for dpid (BUG 930) + - General cookies overhaul. Patches: corvid +- Support for the letter-spacing property. Patch: Johannes Hofmann, corvid diff --git a/configure.in b/configure.in index cec040b4..bd4bc80d 100644 --- a/configure.in +++ b/configure.in @@ -417,6 +417,7 @@ dnl -------------------- dnl if test "x$enable_cookies" = "xno" ; then CFLAGS="$CFLAGS -DDISABLE_COOKIES" + CXXFLAGS="$CXXFLAGS -DDISABLE_COOKIES" fi if test "x$enable_ipv6" = "xyes" ; then CFLAGS="$CFLAGS -DENABLE_IPV6" @@ -114,7 +114,8 @@ # Set the proxy information for http. # Note that the http_proxy environment variable overrides this setting. # WARNING: FTP and downloads plugins use wget. To use a proxy with them, -# you will need to configure wget accordingly. +# you will need to configure wget accordingly. See +# http://www.gnu.org/software/wget/manual/html_node/Proxies.html # http_proxy="http://localhost:8080/" #(by default, no proxy is used) diff --git a/doc/Cookies.txt b/doc/Cookies.txt index c43cacc4..c46e5580 100644 --- a/doc/Cookies.txt +++ b/doc/Cookies.txt @@ -1,13 +1,15 @@ Jan 2002, Jörgen Viksell - jorgen.viksell@telia.com, Jorge Arellano Cid -- -Last update: October 2008 +Last update: January 2010 ================== Cookies in Dillo ================== -Supported: old Netscape style, RFC 2109, RFC 2965. +Dillo's cookies implementation is guided by ongoing work by the HTTP State WG +( http://www.ietf.org/dyn/wg/charter/httpstate-charter ) to specify current +real-world cookies usage. Cookies are handled by a dpi (plugin) which shares them between your instances of Dillo. diff --git a/dpi/cookies.c b/dpi/cookies.c index cf0cecf0..207e3bba 100644 --- a/dpi/cookies.c +++ b/dpi/cookies.c @@ -13,19 +13,17 @@ * */ -/* Handling of cookies takes place here. - * This implementation aims to follow RFC 2965: - * http://www.ietf.org/rfc/rfc2965.txt - */ - -/* - * TODO: Cleanup this code. Shorten some functions, order things, - * add comments, remove leaks, etc. - */ - -/* TODO: this server is not assembling the received packets. - * This means it currently expects dillo to send full dpi tags - * within the socket; if that fails, everything stops. +/* This is written to follow the HTTP State Working Group's + * draft-ietf-httpstate-cookie-01.txt. + * + * We depart from the draft spec's domain format in that, rather than + * using a host-only flag, we continue to use the .domain notation + * internally to indicate cookies that may also be returned to subdomains. + * + * Info on cookies in the wild: + * http://www.ietf.org/mail-archive/web/http-state/current/msg00078.html + * And dates specifically: + * http://www.ietf.org/mail-archive/web/http-state/current/msg00128.html */ #ifdef DISABLE_COOKIES @@ -64,6 +62,7 @@ int main(void) #define _MSG(...) #define MSG(...) printf("[cookies dpi]: " __VA_ARGS__) +#define DILLO_TIME_MAX ((time_t) ((1UL << (sizeof(time_t) * 8 - 1)) - 1)) /* * a_List_add() @@ -107,12 +106,9 @@ typedef struct { char *domain; char *path; time_t expires_at; - uint_t version; - char *comment; - char *comment_url; bool_t secure; bool_t session_only; - Dlist *ports; + long last_used; } CookieData_t; typedef struct { @@ -133,12 +129,13 @@ static int num_ccontrol = 0; static int num_ccontrol_max = 1; static CookieControlAction default_action = COOKIE_DENY; +static long cookies_use_counter = 0; static bool_t disabled; static FILE *file_stream; -static char *cookies_txt_header_str = +static const char *const cookies_txt_header_str = "# HTTP Cookie File\n" -"# http://wp.netscape.com/newsref/std/cookie_spec.html\n" -"# This is a generated file! Do not edit.\n\n"; +"# This is a generated file! Do not edit.\n" +"# [domain TRUE path secure expiry_time name value]\n\n"; /* @@ -147,12 +144,7 @@ static char *cookies_txt_header_str = static CookieControlAction Cookies_control_check_domain(const char *domain); static int Cookie_control_init(void); -static void Cookies_parse_ports(int url_port, CookieData_t *cookie, - const char *port_str); -static char *Cookies_build_ports_str(CookieData_t *cookie); -static char *Cookies_strip_path(const char *path); static void Cookies_add_cookie(CookieData_t *cookie); -static void Cookies_remove_cookie(CookieData_t *cookie); static int Cookies_cmp(const void *a, const void *b); /* @@ -162,7 +154,7 @@ static int Cookie_node_cmp(const void *v1, const void *v2) { const CookieNode *n1 = v1, *n2 = v2; - return strcmp(n1->domain, n2->domain); + return dStrcasecmp(n1->domain, n2->domain); } /* @@ -173,7 +165,7 @@ static int Cookie_node_by_domain_cmp(const void *v1, const void *v2) const CookieNode *node = v1; const char *domain = v2; - return strcmp(node->domain, domain); + return dStrcasecmp(node->domain, domain); } /* @@ -181,7 +173,7 @@ static int Cookie_node_by_domain_cmp(const void *v1, const void *v2) * with the optional 'init_str' as its content. */ static FILE *Cookies_fopen(const char *filename, const char *mode, - char *init_str) + const char *init_str) { FILE *F_in; int fd, rc; @@ -220,15 +212,12 @@ static void Cookies_free_cookie(CookieData_t *cookie) dFree(cookie->value); dFree(cookie->domain); dFree(cookie->path); - dFree(cookie->comment); - dFree(cookie->comment_url); - dList_free(cookie->ports); dFree(cookie); } /* * Initialize the cookies module - * (The 'disabled' variable is writable only within Cookies_init) + * (The 'disabled' variable is writeable only within Cookies_init) */ static void Cookies_init() { @@ -238,7 +227,6 @@ static void Cookies_init() #ifndef HAVE_LOCKF struct flock lck; #endif - FILE *old_cookies_file_stream; /* Default setting */ disabled = TRUE; @@ -256,7 +244,7 @@ static void Cookies_init() dFree(filename); if (!file_stream) { - MSG("ERROR: Can't open ~/.dillo/cookies.txt, disabling cookies\n"); + MSG("ERROR: Can't open ~/.dillo/cookies.txt; disabling cookies\n"); return; } @@ -272,12 +260,12 @@ static void Cookies_init() disabled = (fcntl(fileno(file_stream), F_SETLK, &lck) == -1); #endif if (disabled) { - MSG("The cookies file has a file lock: disabling cookies!\n"); + MSG("The cookies file has a file lock; disabling cookies!\n"); fclose(file_stream); return; } - MSG("Enabling cookies as from cookiesrc...\n"); + MSG("Enabling cookies as per cookiesrc...\n"); cookies = dList_new(32); @@ -286,8 +274,7 @@ static void Cookies_init() line[0] = '\0'; rc = fgets(line, LINE_MAXLEN, file_stream); if (!rc && ferror(file_stream)) { - MSG("Cookies1: Error while reading from cookies.txt: %s\n", - dStrerror(errno)); + MSG("Error while reading from cookies.txt: %s\n", dStrerror(errno)); break; /* bail out */ } @@ -300,7 +287,7 @@ static void Cookies_init() * pieces[0] The domain name * pieces[1] TRUE/FALSE: is the domain a suffix, or a full domain? * pieces[2] The path - * pieces[3] Is the cookie unsecure or secure (TRUE/FALSE) + * pieces[3] TRUE/FALSE: is the cookie for secure use only? * pieces[4] Timestamp of expire date * pieces[5] Name of the cookie * pieces[6] Value of the cookie @@ -312,7 +299,6 @@ static void Cookies_init() cookie = dNew0(CookieData_t, 1); cookie->session_only = FALSE; - cookie->version = 0; cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); dStrsep(&line_marker, "\t"); /* we use domain always as sufix */ cookie->path = dStrdup(dStrsep(&line_marker, "\t")); @@ -323,12 +309,11 @@ static void Cookies_init() if (piece != NULL) cookie->expires_at = (time_t) strtol(piece, NULL, 10); cookie->name = dStrdup(dStrsep(&line_marker, "\t")); - cookie->value = dStrdup(dStrsep(&line_marker, "\t")); + cookie->value = dStrdup(line_marker ? line_marker : ""); if (!cookie->domain || cookie->domain[0] == '\0' || !cookie->path || cookie->path[0] != '/' || - !cookie->name || cookie->name[0] == '\0' || - !cookie->value) { + !cookie->name || !cookie->value) { MSG("Malformed line in cookies.txt file!\n"); Cookies_free_cookie(cookie); continue; @@ -346,86 +331,6 @@ static void Cookies_init() Cookies_add_cookie(cookie); } } - - filename = dStrconcat(dGethomedir(), "/.dillo/cookies", NULL); - if ((old_cookies_file_stream = fopen(filename, "r")) != NULL) { - MSG("WARNING: Reading old cookies file ~/.dillo/cookies too\n"); - - /* Get all lines in the file */ - while (!feof(old_cookies_file_stream)) { - line[0] = '\0'; - rc = fgets(line, LINE_MAXLEN, old_cookies_file_stream); - if (!rc && ferror(old_cookies_file_stream)) { - MSG("Cookies2: Error while reading from cookies file: %s\n", - dStrerror(errno)); - break; /* bail out */ - } - - /* Remove leading and trailing whitespaces */ - dStrstrip(line); - - if (line[0] != '\0') { - /* - * Split the row into pieces using a tab as the delimiter. - * pieces[0] The version this cookie was set as (0 / 1) - * pieces[1] The domain name - * pieces[2] A comma separated list of accepted ports - * pieces[3] The path - * pieces[4] Is the cookie unsecure or secure (0 / 1) - * pieces[5] Timestamp of expire date - * pieces[6] Name of the cookie - * pieces[7] Value of the cookie - * pieces[8] Comment - * pieces[9] Comment url - */ - CookieControlAction action; - char *piece; - char *line_marker = line; - - cookie = dNew0(CookieData_t, 1); - - cookie->session_only = FALSE; - piece = dStrsep(&line_marker, "\t"); - if (piece != NULL) - cookie->version = strtol(piece, NULL, 10); - cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); - Cookies_parse_ports(0, cookie, dStrsep(&line_marker, "\t")); - cookie->path = dStrdup(dStrsep(&line_marker, "\t")); - piece = dStrsep(&line_marker, "\t"); - if (piece != NULL && piece[0] == '1') - cookie->secure = TRUE; - piece = dStrsep(&line_marker, "\t"); - if (piece != NULL) - cookie->expires_at = (time_t) strtol(piece, NULL, 10); - cookie->name = dStrdup(dStrsep(&line_marker, "\t")); - cookie->value = dStrdup(dStrsep(&line_marker, "\t")); - cookie->comment = dStrdup(dStrsep(&line_marker, "\t")); - cookie->comment_url = dStrdup(dStrsep(&line_marker, "\t")); - - if (!cookie->domain || cookie->domain[0] == '\0' || - !cookie->path || cookie->path[0] != '/' || - !cookie->name || cookie->name[0] == '\0' || - !cookie->value) { - MSG("Malformed line in cookies file!\n"); - Cookies_free_cookie(cookie); - continue; - } - - action = Cookies_control_check_domain(cookie->domain); - if (action == COOKIE_DENY) { - Cookies_free_cookie(cookie); - continue; - } else if (action == COOKIE_ACCEPT_SESSION) { - cookie->session_only = TRUE; - } - - /* Save cookie in memory */ - Cookies_add_cookie(cookie); - } - } - fclose(old_cookies_file_stream); - } - dFree(filename); } /* @@ -436,6 +341,7 @@ static void Cookies_save_and_free() int i, fd; CookieNode *node; CookieData_t *cookie; + time_t now; #ifndef HAVE_LOCKF struct flock lck; @@ -444,6 +350,8 @@ static void Cookies_save_and_free() if (disabled) return; + now = time(NULL); + rewind(file_stream); fd = fileno(file_stream); if (ftruncate(fd, 0) == -1) @@ -453,8 +361,7 @@ static void Cookies_save_and_free() /* Iterate cookies per domain, saving and freeing */ while ((node = dList_nth_data(cookies, 0))) { for (i = 0; (cookie = dList_nth_data(node->dlist, i)); ++i) { - if (!cookie->session_only) { - /* char * ports_str = Cookies_build_ports_str(cookie); */ + if (!cookie->session_only && (cookie->expires_at > now)) { fprintf(file_stream, "%s\tTRUE\t%s\t%s\t%ld\t%s\t%s\n", cookie->domain, cookie->path, @@ -462,7 +369,6 @@ static void Cookies_save_and_free() (long)cookie->expires_at, cookie->name, cookie->value); - /* dFree(ports_str); */ } Cookies_free_cookie(cookie); @@ -486,20 +392,19 @@ static void Cookies_save_and_free() fclose(file_stream); } -static char *months[] = -{ "", - "Jan", "Feb", "Mar", - "Apr", "May", "Jun", - "Jul", "Aug", "Sep", - "Oct", "Nov", "Dec" -}; - /* * Take a months name and return a number between 1-12. * E.g. 'April' -> 4 */ static int Cookies_get_month(const char *month_name) { + static const char *const months[] = + { "", + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" + }; int i; for (i = 1; i <= 12; i++) { @@ -520,20 +425,29 @@ static int Cookies_get_month(const char *month_name) * Tue May 21 13:46:22 1991\n * Tue May 21 13:46:22 1991 * + * Let's add: + * Mon Jan 11 08:00:00 2010 GMT + * * (return 0 on malformed date string syntax) + * + * NOTE that the draft spec wants user agents to be more flexible in what + * they accept. For now, let's hack in special cases when they're encountered. + * Why? Because this function is currently understandable, and I don't want to + * abandon that (or at best decrease that -- see section 5.1.1) until there + * is known to be good reason. */ static time_t Cookies_create_timestamp(const char *expires) { time_t ret; int day, month, year, hour, minutes, seconds; char *cp; - char *E_msg = + const char *const E_msg = "Expire date is malformed!\n" " (should be RFC-1123 | RFC-850 | ANSI asctime)\n" - " Ignoring cookie: "; + " Discarding cookie: "; cp = strchr(expires, ','); - if (!cp && (strlen(expires) == 24 || strlen(expires) == 25)) { + if (!cp && strlen(expires)>20 && expires[13] == ':' && expires[16] == ':') { /* Looks like ANSI asctime format... */ cp = (char *)expires; day = strtol(cp + 8, NULL, 10); /* day */ @@ -583,63 +497,34 @@ static time_t Cookies_create_timestamp(const char *expires) (minutes * 60) + seconds); - MSG("Expires in %ld seconds, at %s", - (long)ret - time(NULL), ctime(&ret)); + /* handle overflow */ + if (year >= 1970 && ret < 0) + ret = DILLO_TIME_MAX; return ret; } /* - * Parse a string containing a list of port numbers. - */ -static void Cookies_parse_ports(int url_port, CookieData_t *cookie, - const char *port_str) -{ - if ((!port_str || !port_str[0]) && url_port != 0) { - /* There was no list, so only the calling urls port should be allowed. */ - if (!cookie->ports) - cookie->ports = dList_new(1); - dList_append(cookie->ports, INT2VOIDP(url_port)); - } else if (port_str[0] == '"' && port_str[1] != '"') { - char *tok, *str; - int port; - - str = dStrdup(port_str + 1); - while ((tok = dStrsep(&str, ","))) { - port = strtol(tok, NULL, 10); - if (port > 0) { - if (!cookie->ports) - cookie->ports = dList_new(1); - dList_append(cookie->ports, INT2VOIDP(port)); - } - } - dFree(str); - } -} - -/* - * Build a string of the ports in 'cookie'. + * Remove the least recently used cookie in the list. */ -static char *Cookies_build_ports_str(CookieData_t *cookie) +static void Cookies_remove_LRU(Dlist *cookies) { - Dstr *dstr; - char *ret; - void *data; - int i; + int n = dList_length(cookies); - dstr = dStr_new("\""); - for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { - dStr_sprintfa(dstr, "%d,", VOIDP2INT(data)); - } - /* Remove any trailing comma */ - if (dstr->len > 1) - dStr_erase(dstr, dstr->len - 1, 1); - dStr_append(dstr, "\""); + if (n > 0) { + int i; + CookieData_t *lru = dList_nth_data(cookies, 0); - ret = dstr->str; - dStr_free(dstr, FALSE); + for (i = 1; i < n; i++) { + CookieData_t *curr = dList_nth_data(cookies, i); - return ret; + if (curr->last_used < lru->last_used) + lru = curr; + } + dList_remove(cookies, lru); + MSG("removed LRU cookie \'%s=%s\'\n", lru->name, lru->value); + Cookies_free_cookie(lru); + } } static void Cookies_add_cookie(CookieData_t *cookie) @@ -648,438 +533,461 @@ static void Cookies_add_cookie(CookieData_t *cookie) CookieData_t *c; CookieNode *node; - /* Don't add an expired cookie */ - if (!cookie->session_only && cookie->expires_at < time(NULL)) { - Cookies_free_cookie(cookie); - return; - } - node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); domain_cookies = (node) ? node->dlist : NULL; if (domain_cookies) { + /* Remove any cookies with the same name and path */ + while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))) { + dList_remove(domain_cookies, c); + Cookies_free_cookie(c); + } + /* Respect the limit of 20 cookies per domain */ if (dList_length(domain_cookies) >= 20) { MSG("There are too many cookies for this domain (%s)\n", cookie->domain); - Cookies_free_cookie(cookie); - return; - } - - /* Remove any cookies with the same name and path */ - while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))){ - Cookies_remove_cookie(c); + Cookies_remove_LRU(domain_cookies); } } - /* add the cookie into the respective domain list */ - node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); - domain_cookies = (node) ? node->dlist : NULL; - if (!domain_cookies) { - domain_cookies = dList_new(5); - dList_append(domain_cookies, cookie); - node = dNew(CookieNode, 1); - node->domain = dStrdup(cookie->domain); - node->dlist = domain_cookies; - dList_insert_sorted(cookies, node, Cookie_node_cmp); + /* Don't add an expired cookie. Whether expiring now == expired, exactly, + * is arguable, but we definitely do not want to add a Max-Age=0 cookie. + */ + if (cookie->expires_at <= time(NULL)) { + MSG("Goodbye, expired cookie %s=%s d:%s p:%s\n", cookie->name, + cookie->value, cookie->domain, cookie->path); + Cookies_free_cookie(cookie); } else { - dList_append(domain_cookies, cookie); + cookie->last_used = cookies_use_counter++; + + /* add cookie to domain list */ + if (!domain_cookies) { + domain_cookies = dList_new(5); + dList_append(domain_cookies, cookie); + node = dNew(CookieNode, 1); + node->domain = dStrdup(cookie->domain); + node->dlist = domain_cookies; + dList_insert_sorted(cookies, node, Cookie_node_cmp); + } else { + dList_append(domain_cookies, cookie); + } + } + if (domain_cookies && (dList_length(domain_cookies) == 0)) { + dList_remove(cookies, node); + dFree(node->domain); + dList_free(domain_cookies); + dFree(node); } } /* - * Remove the cookie from the domain list. - * If the domain list is empty, remove the node too. - * Free the cookie. + * Return the attribute that is present at *cookie_str. */ -static void Cookies_remove_cookie(CookieData_t *cookie) +static char *Cookies_parse_attr(char **cookie_str) { - CookieNode *node; + char *str; + uint_t len; - node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); - if (node) { - dList_remove(node->dlist, cookie); - if (dList_length(node->dlist) == 0) { - dList_remove(cookies, node); - dFree(node->domain); - dList_free(node->dlist); - } - } else { - MSG("Attempting to remove a cookie that doesn't exist!\n"); - } + while (dIsspace(**cookie_str)) + (*cookie_str)++; + + str = *cookie_str; + /* find '=' at end of attr, ';' after attr/val pair, '\0' end of string */ + len = strcspn(str, "=;"); + *cookie_str += len; - Cookies_free_cookie(cookie); + while (len && (str[len - 1] == ' ' || str[len - 1] == '\t')) + len--; + return dStrndup(str, len); } /* - * Return the attribute that is present at *cookie_str. This function - * will also attempt to advance cookie_str past any equal-sign. + * Get the value in *cookie_str. */ -static char *Cookies_parse_attr(char **cookie_str) +static char *Cookies_parse_value(char **cookie_str) { - char *str = *cookie_str; - uint_t i, end = 0; - bool_t got_attr = FALSE; - - for (i = 0; ; i++) { - switch (str[i]) { - case ' ': - case '\t': - case '=': - case ';': - got_attr = TRUE; - if (end == 0) - end = i; - break; - case ',': - *cookie_str = str + i; - return dStrndup(str, i); - break; - case '\0': - if (!got_attr) { - end = i; - got_attr = TRUE; - } - /* fall through! */ - default: - if (got_attr) { - *cookie_str = str + i; - return dStrndup(str, end); - } - break; - } - } + uint_t len; + char *str; + + if (**cookie_str == '=') { + (*cookie_str)++; + while (dIsspace(**cookie_str)) + (*cookie_str)++; - return NULL; + str = *cookie_str; + /* finds ';' after attr/val pair or '\0' at end of string */ + len = strcspn(str, ";"); + *cookie_str += len; + + while (len && (str[len - 1] == ' ' || str[len - 1] == '\t')) + len--; + } else { + str = *cookie_str; + len = 0; + } + return dStrndup(str, len); } /* - * Get the value starting at *cookie_str. - * broken_syntax: watch out for stupid syntax (comma in unquoted string...) + * Advance past any value */ -static char *Cookies_parse_value(char **cookie_str, - bool_t broken_syntax, - bool_t keep_quotes) +static void Cookies_eat_value(char **cookie_str) { - uint_t i, end; - char *str = *cookie_str; - - for (i = end = 0; !end; ++i) { - switch (str[i]) { - case ' ': - case '\t': - if (!broken_syntax && str[0] != '\'' && str[0] != '"') { - *cookie_str = str + i + 1; - end = 1; - } - break; - case '\'': - case '"': - if (i != 0 && str[i] == str[0]) { - char *tmp = str + i; - - while (*tmp != '\0' && *tmp != ';' && *tmp != ',') - tmp++; + if (**cookie_str == '=') + *cookie_str += strcspn(*cookie_str, ";"); +} - *cookie_str = (*tmp == ';') ? tmp + 1 : tmp; +/* + * Handle Expires attribute. + * Note that this CAN MODIFY the value string. + */ +static time_t Cookies_expires_attr(char *value, const char *server_date) +{ + time_t exptime; - if (keep_quotes) - i++; - end = 1; - } - break; - case '\0': - *cookie_str = str + i; - end = 1; - break; - case ',': - if (str[0] != '\'' && str[0] != '"' && !broken_syntax) { - /* A new cookie starts here! */ - *cookie_str = str + i; - end = 1; - } - break; - case ';': - if (str[0] != '\'' && str[0] != '"') { - *cookie_str = str + i + 1; - end = 1; + if (*value == '"' && value[strlen(value) - 1] == '"') { + /* In this one case, pay attention to quotes */ + value[strlen(value) - 1] = '\0'; + value++; + } + exptime = Cookies_create_timestamp(value); + if (exptime && server_date) { + time_t server_time = Cookies_create_timestamp(server_date); + + if (server_time) { + time_t now = time(NULL); + time_t client_time = exptime + now - server_time; + + if (server_time == exptime) { + exptime = now; + } else if ((exptime > now) == (client_time > now)) { + exptime = client_time; + } else { + /* Don't want to wrap around at the extremes of representable + * values thanks to clock skew. + */ + MSG("At %ld, %ld was trying to turn into %ld\n", + (long)now, (long)exptime, + (long)client_time); } - break; - default: - break; } } - /* keep i as an index to the last char */ - --i; - - if ((str[0] == '\'' || str[0] == '"') && !keep_quotes) { - return i > 1 ? dStrndup(str + 1, i - 1) : NULL; - } else { - return dStrndup(str, i); - } + return exptime; } /* - * Parse one cookie... + * Parse cookie. A cookie might look something like: + * "Name=Val; Domain=example.com; Max-Age=3600; HttpOnly" */ -static CookieData_t *Cookies_parse_one(int url_port, char **cookie_str) +static CookieData_t *Cookies_parse(char *cookie_str, const char *server_date) { - CookieData_t *cookie; - char *str = *cookie_str; - char *attr; - char *value; - int num_attr = 0; + CookieData_t *cookie = NULL; + char *str = cookie_str; + bool_t first_attr = TRUE; bool_t max_age = FALSE; - bool_t discard = FALSE; - bool_t error = FALSE; - - cookie = dNew0(CookieData_t, 1); - cookie->session_only = TRUE; + bool_t expires = FALSE; - /* Iterate until there is nothing left of the string OR we come - * across a comma representing the start of another cookie */ - while (*str != '\0' && *str != ',') { - if (error) { - str++; - continue; - } - /* Skip whitespace */ - while (dIsspace(*str)) - str++; + /* Iterate until there is nothing left of the string */ + while (*str) { + char *attr; + char *value; /* Get attribute */ attr = Cookies_parse_attr(&str); - if (!attr) { - MSG("Cannot parse cookie attribute!\n"); - error = TRUE; - continue; - } /* Get the value for the attribute and store it */ - if (num_attr == 0) { - /* The first attr, which always is the user supplied attr, may - * have the same name as an ordinary attr. Hence this workaround. */ - cookie->name = dStrdup(attr); - cookie->value = Cookies_parse_value(&str, FALSE, TRUE); + if (first_attr) { + if (!*str && !*attr) { + dFree(attr); + return NULL; + } + cookie = dNew0(CookieData_t, 1); + /* let's arbitrarily choose a year for now */ + cookie->expires_at = time(NULL) + 60 * 60 * 24 * 365; + + if (*str != '=') { + /* NOTE it seems possible that the Working Group will decide + * against allowing nameless cookies. + */ + cookie->name = dStrdup(""); + cookie->value = attr; + } else { + cookie->name = dStrdup(attr); + cookie->value = Cookies_parse_value(&str); + } } else if (dStrcasecmp(attr, "Path") == 0) { - value = Cookies_parse_value(&str, FALSE, FALSE); + value = Cookies_parse_value(&str); + dFree(cookie->path); cookie->path = value; } else if (dStrcasecmp(attr, "Domain") == 0) { - value = Cookies_parse_value(&str, FALSE, FALSE); + value = Cookies_parse_value(&str); + dFree(cookie->domain); cookie->domain = value; - } else if (dStrcasecmp(attr, "Discard") == 0) { - cookie->session_only = TRUE; - discard = TRUE; } else if (dStrcasecmp(attr, "Max-Age") == 0) { - if (!discard) { - value = Cookies_parse_value(&str, FALSE, FALSE); - - if (value) { - cookie->expires_at = time(NULL) + strtol(value, NULL, 10); - cookie->session_only = FALSE; - max_age = TRUE; - dFree(value); - } else { - MSG("Cannot parse cookie Max-Age value!\n"); - dFree(attr); - error = TRUE; - continue; + value = Cookies_parse_value(&str); + if (isdigit(*value) || *value == '-') { + time_t now = time(NULL); + long age = strtol(value, NULL, 10); + + cookie->expires_at = now + age; + if (age > 0 && cookie->expires_at < 0) { + /* handle overflow */ + cookie->expires_at = DILLO_TIME_MAX; } + expires = max_age = TRUE; } - } else if (dStrcasecmp(attr, "Expires") == 0) { - if (!max_age && !discard) { - MSG("Old netscape-style cookie...\n"); - value = Cookies_parse_value(&str, TRUE, FALSE); - if (value) { - cookie->expires_at = Cookies_create_timestamp(value); - cookie->session_only = FALSE; - dFree(value); - } else { - MSG("Cannot parse cookie Expires value!\n"); - dFree(attr); - error = TRUE; - continue; - } - } - } else if (dStrcasecmp(attr, "Port") == 0) { - value = Cookies_parse_value(&str, FALSE, TRUE); - Cookies_parse_ports(url_port, cookie, value); dFree(value); - } else if (dStrcasecmp(attr, "Comment") == 0) { - value = Cookies_parse_value(&str, FALSE, FALSE); - cookie->comment = value; - } else if (dStrcasecmp(attr, "CommentURL") == 0) { - value = Cookies_parse_value(&str, FALSE, FALSE); - cookie->comment_url = value; - } else if (dStrcasecmp(attr, "Version") == 0) { - value = Cookies_parse_value(&str, FALSE, FALSE); - - if (value) { - cookie->version = strtol(value, NULL, 10); + } else if (dStrcasecmp(attr, "Expires") == 0) { + if (!max_age) { + value = Cookies_parse_value(&str); + cookie->expires_at = Cookies_expires_attr(value, server_date); + expires = TRUE; dFree(value); - } else { - MSG("Cannot parse cookie Version value!\n"); - dFree(attr); - error = TRUE; - continue; + MSG("Expires in %ld seconds, at %s", + (long)cookie->expires_at - time(NULL), + ctime(&cookie->expires_at)); + } } else if (dStrcasecmp(attr, "Secure") == 0) { cookie->secure = TRUE; + Cookies_eat_value(&str); } else if (dStrcasecmp(attr, "HttpOnly") == 0) { - // this case is intentionally left blank, because we do not - // do client-side scripting (yet). + Cookies_eat_value(&str); } else { - /* Oops! this can't be good... */ MSG("Cookie contains unknown attribute: '%s'\n", attr); - dFree(attr); - error = TRUE; - continue; + Cookies_eat_value(&str); } - dFree(attr); - num_attr++; - } - - *cookie_str = (*str == ',') ? str + 1 : str; + if (first_attr) + first_attr = FALSE; + else + dFree(attr); - if (!error && (!cookie->name || !cookie->value)) { - MSG("Cookie missing name and/or value!\n"); - error = TRUE; - } - if (error) { - Cookies_free_cookie(cookie); - cookie = NULL; + if (*str == ';') + str++; } + cookie->session_only = expires == FALSE; return cookie; } /* - * Iterate the cookie string until we catch all cookies. - * Return Value: a list with all the cookies! (or NULL upon error) + * Compare cookies by name and path (return 0 if equal) */ -static Dlist *Cookies_parse_string(int url_port, char *cookie_string) +static int Cookies_cmp(const void *a, const void *b) { - CookieData_t *cookie; - Dlist *ret = NULL; - char *str = cookie_string; + const CookieData_t *ca = a, *cb = b; + int ret; - /* The string may contain several cookies separated by comma. - * We'll iterate until we've caught them all */ - while (*str) { - cookie = Cookies_parse_one(url_port, &str); + if (!(ret = strcmp(ca->name, cb->name))) + ret = strcmp(ca->path, cb->path); + return ret; +} - if (cookie) { - if (!ret) - ret = dList_new(4); - dList_append(ret, cookie); - } else { - MSG("Malformed cookie field, ignoring cookie: %s\n", cookie_string); - } - } +/* + * Is the domain an IP address? + */ +static bool_t Cookies_domain_is_ip(const char *domain) +{ + uint_t len; - return ret; + if (!domain) + return FALSE; + + len = strlen(domain); + + if (len == strspn(domain, "0123456789.")) { + MSG("an IPv4 address\n"); + return TRUE; + } + if (*domain == '[' && + (len == strspn(domain, "0123456789abcdefABCDEF:.[]"))) { + /* The precise format is shown in section 3.2.2 of rfc 3986 */ + MSG("an IPv6 address\n"); + return TRUE; + } + return FALSE; } /* - * Compare cookies by name and path (return 0 if equal) + * Check whether url_path path-matches cookie_path + * + * Note different user agents apparently vary in path-matching behaviour, + * but this is the recommended method at the moment. */ -static int Cookies_cmp(const void *a, const void *b) +static bool_t Cookies_path_matches(const char *url_path, + const char *cookie_path) { - const CookieData_t *ca = a, *cb = b; - int ret; + bool_t ret = TRUE; - if (!(ret = strcmp(ca->name, cb->name))) - ret = strcmp(ca->path, cb->path); + if (!url_path || !cookie_path) { + ret = FALSE; + } else { + uint_t c_len = strlen(cookie_path); + uint_t u_len = strlen(url_path); + + ret = (!strncmp(cookie_path, url_path, c_len) && + ((c_len == u_len) || + (c_len > 0 && cookie_path[c_len - 1] == '/') || + (url_path[c_len] == '/'))); + } return ret; } /* - * Validate cookies domain against some security checks. + * If cookie path is not properly set, remedy that. */ -static bool_t Cookies_validate_domain(CookieData_t *cookie, char *host, - char *url_path) +static void Cookies_validate_path(CookieData_t *cookie, const char *url_path) { - int dots, diff, i; - bool_t is_ip; - - /* Make sure that the path is set to something */ if (!cookie->path || cookie->path[0] != '/') { dFree(cookie->path); - cookie->path = Cookies_strip_path(url_path); + + if (url_path) { + uint_t len = strlen(url_path); + + while (len && url_path[len] != '/') + len--; + cookie->path = dStrndup(url_path, len ? len : 1); + } else { + cookie->path = dStrdup("/"); + } } +} - /* If the server never set a domain, or set one without a leading - * dot (which isn't allowed), we use the calling URL's hostname. */ - if (cookie->domain == NULL || cookie->domain[0] != '.') { - dFree(cookie->domain); - cookie->domain = dStrdup(host); +/* + * Check whether host name A domain-matches host name B. + */ +static bool_t Cookies_domain_matches(char *A, char *B) +{ + int diff; + + if (!A || !*A || !B || !*B) + return FALSE; + + if (*B == '.') + B++; + + /* Should we concern ourselves with trailing dots in matching (here or + * elsewhere)? The HTTP State people have found that most user agents + * don't, so: No. + */ + + if (!dStrcasecmp(A, B)) return TRUE; - } - /* Count the number of dots and also find out if it is an IP-address */ - is_ip = TRUE; - for (i = 0, dots = 0; cookie->domain[i] != '\0'; i++) { - if (cookie->domain[i] == '.') - dots++; - else if (!isdigit(cookie->domain[i])) - is_ip = FALSE; - } + if (Cookies_domain_is_ip(B)) + return FALSE; + + diff = strlen(A) - strlen(B); - /* A valid domain must have at least two dots in it */ - /* NOTE: this breaks cookies on localhost... */ - if (dots < 2) { + if (diff > 0) { + /* B is the tail of A, and the match is preceded by a '.' */ + return (dStrcasecmp(A + diff, B) == 0 && A[diff - 1] == '.'); + } else { return FALSE; } +} - /* Now see if the url matches the domain */ - diff = strlen(host) - i; - if (diff > 0) { - if (dStrcasecmp(host + diff, cookie->domain)) - return FALSE; - - if (!is_ip) { - /* "x.y.test.com" is not allowed to set cookies for ".test.com"; - * only an url of the form "y.test.com" would be. */ - while ( diff-- ) - if (host[diff] == '.') - return FALSE; +/* + * Based on the host, how many internal dots do we need in a cookie domain + * to make it valid? e.g., "org" is not on the list, so dillo.org is a safe + * cookie domain, but "uk" is on the list, so ac.uk is not safe. + * + * This is imperfect, but it's something. Specifically, checking for these + * TLDs is the solution that Konqueror used once upon a time, according to + * reports. + */ +static uint_t Cookies_internal_dots_required(const char *host) +{ + uint_t ret = 1; + + if (host) { + int start, after, tld_len; + + /* We may be able to trust the format of the host string more than + * I am here. Trailing dots and no dots are real possibilities, though. + */ + after = strlen(host); + if (after > 0 && host[after - 1] == '.') + after--; + start = after; + while (start > 0 && host[start - 1] != '.') + start--; + tld_len = after - start; + + if (tld_len > 0) { + /* These TLDs were chosen by examining the current publicsuffix list + * in January 2010 and picking out those where it was simplest for + * them to describe the situation by beginning with a "*.[tld]" rule. + */ + const char *const tlds[] = {"ar","au","bd","bn","bt","ck","cy","do", + "eg","er","et","fj","fk","gt","gu","id", + "il","jm","ke","kh","kw","ml","mm","mt", + "mz","ni","np","nz","om","pg","py","qa", + "sv","tr","uk","uy","ve","ye","yu","za", + "zm","zw"}; + uint_t i, tld_num = sizeof(tlds) / sizeof(tlds[0]); + + for (i = 0; i < tld_num; i++) { + if (strlen(tlds[i]) == (uint_t) tld_len && + !dStrncasecmp(tlds[i], host + start, tld_len)) { + MSG("TLD code matched %s\n", tlds[i]); + ret++; + break; + } + } } } - - return TRUE; + return ret; } /* - * Strip of the filename from a full path + * Validate cookies domain against some security checks. */ -static char *Cookies_strip_path(const char *path) +static bool_t Cookies_validate_domain(CookieData_t *cookie, char *host) { - char *ret; - uint_t len; + uint_t i, internal_dots; + + if (!cookie->domain) { + cookie->domain = dStrdup(host); + return TRUE; + } - if (path) { - len = strlen(path); + if (cookie->domain[0] != '.' && !Cookies_domain_is_ip(cookie->domain)) { + char *d = dStrconcat(".", cookie->domain, NULL); + dFree(cookie->domain); + cookie->domain = d; + } - while (len && path[len] != '/') - len--; - ret = dStrndup(path, len + 1); - } else { - ret = dStrdup("/"); + if (!Cookies_domain_matches(host, cookie->domain)) + return FALSE; + + internal_dots = 0; + for (i = 1; i < strlen(cookie->domain) - 1; i++) { + if (cookie->domain[i] == '.') + internal_dots++; } - return ret; + /* All of this dots business is a weak hack. + * TODO: accept the publicsuffix.org list as an optional external file. + */ + if (internal_dots < Cookies_internal_dots_required(host)) { + MSG("not enough dots in %s\n", cookie->domain); + return FALSE; + } + + MSG("host %s and domain %s is all right\n", host, cookie->domain); + return TRUE; } /* * Set the value corresponding to the cookie string */ static void Cookies_set(char *cookie_string, char *url_host, - char *url_path, int url_port) + char *url_path, char *server_date) { CookieControlAction action; CookieData_t *cookie; - Dlist *list; - int i; if (disabled) return; @@ -1090,65 +998,96 @@ static void Cookies_set(char *cookie_string, char *url_host, return; } - if ((list = Cookies_parse_string(url_port, cookie_string))) { - for (i = 0; (cookie = dList_nth_data(list, i)); ++i) { - if (Cookies_validate_domain(cookie, url_host, url_path)) { - if (action == COOKIE_ACCEPT_SESSION) - cookie->session_only = TRUE; - Cookies_add_cookie(cookie); - } else { - MSG("Rejecting cookie for %s from host %s path %s\n", - cookie->domain, url_host, url_path); - Cookies_free_cookie(cookie); - } + MSG("%s SETTING: %s\n", url_host, cookie_string); + + if ((cookie = Cookies_parse(cookie_string, server_date))) { + if (Cookies_validate_domain(cookie, url_host)) { + Cookies_validate_path(cookie, url_path); + if (action == COOKIE_ACCEPT_SESSION) + cookie->session_only = TRUE; + Cookies_add_cookie(cookie); + } else { + MSG("Rejecting cookie for domain %s from host %s path %s\n", + cookie->domain, url_host, url_path); + Cookies_free_cookie(cookie); } - dList_free(list); } } /* - * Compare the cookie with the supplied data to see if it matches + * Compare the cookie with the supplied data to see whether it matches */ -static bool_t Cookies_match(CookieData_t *cookie, int port, - const char *path, bool_t is_ssl) +static bool_t Cookies_match(CookieData_t *cookie, const char *url_path, + bool_t is_ssl) { - void *data; - int i; - /* Insecure cookies matches both secure and insecure urls, secure cookies matches only secure urls */ if (cookie->secure && !is_ssl) return FALSE; - /* Check that the cookie path is a subpath of the current path */ - if (strncmp(cookie->path, path, strlen(cookie->path)) != 0) + if (!Cookies_path_matches(url_path, cookie->path)) return FALSE; - /* Check if the port of the request URL matches any - * of those set in the cookie */ - if (cookie->ports) { - for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { - if (VOIDP2INT(data) == port) - return TRUE; - } - return FALSE; - } - /* It's a match */ return TRUE; } +static void Cookies_add_matching_cookies(const char *domain, + const char *url_path, + Dlist *matching_cookies, + bool_t is_ssl) +{ + CookieNode *node = dList_find_sorted(cookies, domain, + Cookie_node_by_domain_cmp); + if (node) { + int i; + CookieData_t *cookie; + Dlist *domain_cookies = node->dlist; + + for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) { + /* Remove expired cookie. */ + if (cookie->expires_at < time(NULL)) { + MSG("Goodbye, expired cookie %s=%s d:%s p:%s\n", cookie->name, + cookie->value, cookie->domain, cookie->path); + dList_remove(domain_cookies, cookie); + Cookies_free_cookie(cookie); + --i; continue; + } + /* Check if the cookie matches the requesting URL */ + if (Cookies_match(cookie, url_path, is_ssl)) { + int j; + CookieData_t *curr; + uint_t path_length = strlen(cookie->path); + + cookie->last_used = cookies_use_counter; + + /* Longest cookies go first */ + for (j = 0; + (curr = dList_nth_data(matching_cookies, j)) && + strlen(curr->path) >= path_length; + j++) ; + dList_insert_pos(matching_cookies, cookie, j); + } + } + + if (dList_length(domain_cookies) == 0) { + dList_remove(cookies, node); + dFree(node->domain); + dList_free(domain_cookies); + dFree(node); + } + } +} + /* * Return a string that contains all relevant cookies as headers. */ static char *Cookies_get(char *url_host, char *url_path, - char *url_scheme, int url_port) + char *url_scheme) { - char *domain_str, *q, *str, *path; + char *domain_str, *str; CookieData_t *cookie; Dlist *matching_cookies; - CookieNode *node; - Dlist *domain_cookies; bool_t is_ssl; Dstr *cookie_dstring; int i; @@ -1158,64 +1097,45 @@ static char *Cookies_get(char *url_host, char *url_path, matching_cookies = dList_new(8); - path = Cookies_strip_path(url_path); - /* Check if the protocol is secure or not */ is_ssl = (!dStrcasecmp(url_scheme, "https")); for (domain_str = (char *) url_host; domain_str != NULL && *domain_str; domain_str = strchr(domain_str+1, '.')) { - - node = dList_find_sorted(cookies, domain_str, Cookie_node_by_domain_cmp); - domain_cookies = (node) ? node->dlist : NULL; - - for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) { - /* Remove expired cookie. */ - if (!cookie->session_only && cookie->expires_at < time(NULL)) { - Cookies_remove_cookie(cookie); - --i; continue; - } - /* Check if the cookie matches the requesting URL */ - if (Cookies_match(cookie, url_port, path, is_ssl)) { - dList_append(matching_cookies, cookie); - } - } + Cookies_add_matching_cookies(domain_str, url_path, matching_cookies, + is_ssl); + } + if (!Cookies_domain_is_ip(url_host)) { + domain_str = dStrconcat(".", url_host, NULL); + Cookies_add_matching_cookies(domain_str, url_path, matching_cookies, + is_ssl); + dFree(domain_str); } /* Found the cookies, now make the string */ cookie_dstring = dStr_new(""); if (dList_length(matching_cookies) > 0) { - CookieData_t *first_cookie = dList_nth_data(matching_cookies, 0); dStr_sprintfa(cookie_dstring, "Cookie: "); - if (first_cookie->version != 0) - dStr_sprintfa(cookie_dstring, "$Version=\"%d\"; ", - first_cookie->version); - - for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) { - q = (cookie->version == 0 ? "" : "\""); dStr_sprintfa(cookie_dstring, - "%s=%s; $Path=%s%s%s; $Domain=%s%s%s", - cookie->name, cookie->value, - q, cookie->path, q, q, cookie->domain, q); - if (cookie->ports) { - char *ports_str = Cookies_build_ports_str(cookie); - dStr_sprintfa(cookie_dstring, "; $Port=%s", ports_str); - dFree(ports_str); - } - + "%s%s%s", + cookie->name, *cookie->name ? "=" : "", cookie->value); dStr_append(cookie_dstring, dList_length(matching_cookies) > i + 1 ? "; " : "\r\n"); } } dList_free(matching_cookies); - dFree(path); str = cookie_dstring->str; dStr_free(cookie_dstring, FALSE); + + if (*str) + cookies_use_counter++; + + MSG("%s GETTING: %s\n", url_host, str); return str; } @@ -1255,7 +1175,7 @@ static int Cookie_control_init(void) line[0] = '\0'; rc = fgets(line, LINE_MAXLEN, stream); if (!rc && ferror(stream)) { - MSG("Cookies3: Error while reading rule from cookiesrc: %s\n", + MSG("Error while reading rule from cookiesrc: %s\n", dStrerror(errno)); break; /* bail out */ } @@ -1351,8 +1271,8 @@ static CookieControlAction Cookies_control_check_domain(const char *domain) */ static int srv_parse_tok(Dsh *sh, ClientInfo *client, char *Buf) { - char *p, *cmd, *cookie, *host, *path, *scheme; - int port, ret = 1; + char *cmd, *cookie, *host, *path, *scheme; + int ret = 1; size_t BufSize = strlen(Buf); cmd = a_Dpip_get_attr_l(Buf, BufSize, "cmd"); @@ -1371,16 +1291,17 @@ static int srv_parse_tok(Dsh *sh, ClientInfo *client, char *Buf) exit(0); } else if (cmd && strcmp(cmd, "set_cookie") == 0) { + char *date; + dFree(cmd); cookie = a_Dpip_get_attr_l(Buf, BufSize, "cookie"); host = a_Dpip_get_attr_l(Buf, BufSize, "host"); path = a_Dpip_get_attr_l(Buf, BufSize, "path"); - p = a_Dpip_get_attr_l(Buf, BufSize, "port"); - port = strtol(p, NULL, 10); - dFree(p); + date = a_Dpip_get_attr_l(Buf, BufSize, "date"); - Cookies_set(cookie, host, path, port); + Cookies_set(cookie, host, path, date); + dFree(date); dFree(path); dFree(host); dFree(cookie); @@ -1391,11 +1312,8 @@ static int srv_parse_tok(Dsh *sh, ClientInfo *client, char *Buf) scheme = a_Dpip_get_attr_l(Buf, BufSize, "scheme"); host = a_Dpip_get_attr_l(Buf, BufSize, "host"); path = a_Dpip_get_attr_l(Buf, BufSize, "path"); - p = a_Dpip_get_attr_l(Buf, BufSize, "port"); - port = strtol(p, NULL, 10); - dFree(p); - cookie = Cookies_get(host, path, scheme, port); + cookie = Cookies_get(host, path, scheme); dFree(scheme); dFree(path); dFree(host); diff --git a/dpid/dpid.c b/dpid/dpid.c index b5e53d7e..33c6c31c 100644 --- a/dpid/dpid.c +++ b/dpid/dpid.c @@ -25,6 +25,7 @@ #include <sys/stat.h> #include <sys/wait.h> #include <sys/socket.h> +#include <netinet/tcp.h> #include <unistd.h> #include "dpid_common.h" @@ -519,10 +520,13 @@ int fill_services_list(struct dp *attlist, int numdpis, Dlist **services_list) */ static int make_socket_fd() { - int ret; + int ret, one = 1; if ((ret = socket(AF_INET, SOCK_STREAM, 0)) == -1) { ERRMSG("make_socket_fd", "socket", errno); + } else { + /* avoid delays when sending small pieces of data */ + setsockopt(ret, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); } /* set some buffering to increase the transfer's speed */ diff --git a/dw/style.cc b/dw/style.cc index 6368560f..8d3035d0 100644 --- a/dw/style.cc +++ b/dw/style.cc @@ -134,6 +134,7 @@ bool StyleAttrs::equals (object::Object *other) { whiteSpace == otherAttrs->whiteSpace && listStylePosition == otherAttrs->listStylePosition && listStyleType == otherAttrs->listStyleType && + cursor == otherAttrs->cursor && x_link == otherAttrs->x_link && x_img == otherAttrs->x_img && x_tooltip == otherAttrs->x_tooltip); @@ -166,6 +167,7 @@ int StyleAttrs::hashValue () { whiteSpace + listStylePosition + listStyleType + + cursor + x_link + x_img + (intptr_t) x_tooltip; diff --git a/dw/style.hh b/dw/style.hh index 231f879a..700c56d3 100644 --- a/dw/style.hh +++ b/dw/style.hh @@ -81,7 +81,7 @@ namespace core { * <li> A length refers to an absolute measurement. It is used to * represent the HTML type %Pixels; and the CSS type \<length\>. * - * For CSS lenghts, there are two units: (i) pixels and absolute + * For CSS lengths, there are two units: (i) pixels and absolute * units, which have to be converted to pixels (a pixel is, unlike * in the CSS specification, treated as absolute unit), and (ii) the * relative units "em" and "ex" (see below). @@ -343,7 +343,7 @@ enum WhiteSpace { * <li> dw::core::style::relLengthVal * </ul> * - * "auto" lenghths are represented as dw::core::style::LENGTH_AUTO. + * "auto" lengths are represented as dw::core::style::LENGTH_AUTO. */ typedef int Length; diff --git a/dw/table.hh b/dw/table.hh index b7264336..87bbaa76 100644 --- a/dw/table.hh +++ b/dw/table.hh @@ -14,7 +14,7 @@ namespace dw { * * The dw::Table widget is used to render HTML tables. * - * Each cell is itself an own widget. Any widget may be used, however, in + * Each cell is itself a separate widget. Any widget may be used, however, in * dillo, only instances of dw::Textblock and dw::TableCell are used as * children of dw::Table. * @@ -25,7 +25,7 @@ namespace dw { * * The following diagram shows the dependencies between the different * functions, which are related to size calculation. Click on the boxes - * for more informations. + * for more information. * * \dot * digraph G { @@ -128,7 +128,7 @@ namespace dw { * \f$e_{\hbox{span},i,\min}\f$ (but not \f$e_{\hbox{span},i,\max}\f$) * are calculated from cells with colspan > 1. (In the following formulas, * the cell at \f$(i_1, j)\f$ always span from \f$i_1\f$ to \f$i_2\f$.) - * If the minimal width of the column exeeds the sum of the column minima + * If the minimal width of the column exceeds the sum of the column minima * calculated in the last step: * * \f[e_{\hbox{cell},i_1,j,\min} > @@ -137,7 +137,7 @@ namespace dw { * then the minimal width of this cell is apportioned to the columns: * * <ul> - * <li> If the minimal width of this cell also exeeds the sum of the + * <li> If the minimal width of this cell also exceeds the sum of the * column maxima: * * \f[e_{\hbox{cell},i_1,j,\min} > @@ -170,11 +170,11 @@ namespace dw { * \f[ e_{i,\max} = * \max \{ e_{\hbox{base},i,\max}, e_{i,\min} \} \f] * For the maxima, there is no \f$e_{\hbox{span},i,\max}\f$, but it has to - * be assured, that the maximum is always greater or equal than/to the + * be assured, that the maximum is always greater than or equal to the * minimum. * </ol> * - * Generally, if absolute widths are speficied, they are, instead of the + * Generally, if absolute widths are specified, they are, instead of the * results of dw::core::Widget::getExtremes, taken for the minimal and * maximal width of a cell (minus the box difference, i.e. the difference * between content size and widget size). If the content width @@ -199,15 +199,15 @@ namespace dw { * * <ul> * <li> the specified absolute width of the table, when given, or - * <li> the available width (set by dw::Table::setWidth) times the specifies - * percentage width pf t(at max 100%), if the latter is given, or + * <li> the available width (set by dw::Table::setWidth) times the specified + * percentage width of t(at max 100%), if the latter is given, or * <li> otherwise the available width. * </ul> * * In any case, it is corrected, if it is less than the minimal width * (but not if it is greater than the maximal width). * - * \bug The parantheses is not fully clear, look at the old code. + * \bug The parentheses is not fully clear, look at the old code. * * Details on differences because of styles are omitted. Below, this * total width is called \f$W\f$. @@ -230,7 +230,7 @@ namespace dw { * * <li> Then, calculate the sum of the widths, which the columns with * percentage width specification would allocate, when fully adhering to - * then: + * them: * * \f[W_{\hbox{columns}_\%,\hbox{best}} = W \sum w_{i,\%}\f] * @@ -274,7 +274,7 @@ namespace dw { * <h5>Column Widths</h5> * * The column widths are now simply calculated by applying the - * apportenment function. + * apportionment function. * * * <h5>Row Heights</h5> @@ -286,7 +286,7 @@ namespace dw { * The algorithm described here tends to result in more homogeneous column * widths. * - * The following rule lead to well-defined \f$w_{i}\f$: All columns + * The following rule leads to well-defined \f$w_{i}\f$: All columns * \f$i\f$ have have the same width \f$w\f$, except: * <ul> * <li> \f$w < e_{i,\min}\f$, or diff --git a/dw/textblock.cc b/dw/textblock.cc index d4cc9734..fae2613a 100644 --- a/dw/textblock.cc +++ b/dw/textblock.cc @@ -1437,12 +1437,12 @@ int Textblock::findLineIndex (int y) while ( step > 1 ) { index = low + step; if (index <= maxIndex && - lineYOffsetWidgetI (index) < y) + lineYOffsetWidgetI (index) <= y) low = index; step = (step + 1) >> 1; } - if (low < maxIndex && lineYOffsetWidgetI (low + 1) < y) + if (low < maxIndex && lineYOffsetWidgetI (low + 1) <= y) low++; /* diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am index 98d8d43a..b168073c 100644 --- a/src/IO/Makefile.am +++ b/src/IO/Makefile.am @@ -1,3 +1,4 @@ +AM_CPPFLAGS=-DDILLO_BINDIR='"$(bindir)/"' AM_CFLAGS = @LIBFLTK_CFLAGS@ AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@ diff --git a/src/IO/dpi.c b/src/IO/dpi.c index 6491ea27..6f46b2ba 100644 --- a/src/IO/dpi.c +++ b/src/IO/dpi.c @@ -30,6 +30,7 @@ #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> +#include <netinet/tcp.h> #include <arpa/inet.h> #include <netdb.h> @@ -364,13 +365,17 @@ static int Dpi_start_dpid(void) Dpi_close_fd(st_pipe[0]); if (execl(path1, "dpid", (char*)NULL) == -1) { dFree(path1); - if (execlp("dpid", "dpid", (char*)NULL) == -1) { - MSG("Dpi_start_dpid (child): %s\n", dStrerror(errno)); - if (Dpi_blocking_write(st_pipe[1], "ERROR", 5) == -1) { - MSG("Dpi_start_dpid (child): can't write to pipe.\n"); + path1 = dStrconcat(DILLO_BINDIR, "dpid", NULL); + if (execl(path1, "dpid", (char*)NULL) == -1) { + dFree(path1); + if (execlp("dpid", "dpid", (char*)NULL) == -1) { + MSG("Dpi_start_dpid (child): %s\n", dStrerror(errno)); + if (Dpi_blocking_write(st_pipe[1], "ERROR", 5) == -1) { + MSG("Dpi_start_dpid (child): can't write to pipe.\n"); + } + Dpi_close_fd(st_pipe[1]); + _exit (EXIT_FAILURE); } - Dpi_close_fd(st_pipe[1]); - _exit (EXIT_FAILURE); } } } else if (pid < 0) { @@ -430,9 +435,11 @@ static int Dpi_read_comm_keys(int *port) */ static int Dpi_make_socket_fd() { - int fd, ret = -1; + int fd, one = 1, ret = -1; if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) { + /* avoid delays when sending small pieces of data */ + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ret = fd; } return ret; diff --git a/src/cache.c b/src/cache.c index 3341388c..6df062b3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -728,15 +728,14 @@ static void Cache_parse_header(CacheEntry_t *entry) dFree(encoding); /* free Transfer-Encoding */ #ifndef DISABLE_COOKIES - /* BUG: If a server feels like mixing Set-Cookie2 and Set-Cookie - * responses which aren't identical, then we have a problem. I don't - * know if that is a real issue though. */ - if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie2")) || - (Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { - a_Cookies_set(Cookies, entry->Url); + if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { + char *server_date = Cache_parse_field(header, "Date"); + + a_Cookies_set(Cookies, entry->Url, server_date); for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) dFree(data); dList_free(Cookies); + dFree(server_date); } #endif /* !DISABLE_COOKIES */ diff --git a/src/cookies.c b/src/cookies.c index a25138cd..ea386be5 100644 --- a/src/cookies.c +++ b/src/cookies.c @@ -10,10 +10,9 @@ * (at your option) any later version. */ -/* Handling of cookies takes place here. - * This implementation aims to follow RFC 2965: - * http://www.ietf.org/rfc/rfc2965.txt - */ +/* Handling of cookies takes place here. */ + +#include "msg.h" #ifdef DISABLE_COOKIES @@ -37,7 +36,6 @@ void a_Cookies_init(void) #include <ctype.h> #include <errno.h> -#include "msg.h" #include "IO/Url.h" #include "list.h" #include "cookies.h" @@ -138,10 +136,11 @@ void a_Cookies_freeall() /* * Set the value corresponding to the cookie string */ -void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) +void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url, + const char *date) { CookieControlAction action; - char *cmd, *cookie_string, *dpip_tag, numstr[16]; + char *cmd, *cookie_string, *dpip_tag; const char *path; int i; @@ -156,10 +155,14 @@ void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) for (i = 0; (cookie_string = dList_nth_data(cookie_strings, i)); ++i) { path = URL_PATH_(set_url); - snprintf(numstr, 16, "%d", URL_PORT(set_url)); - cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s port=%s", - "set_cookie", cookie_string, URL_HOST_(set_url), - path ? path : "/", numstr); + if (date) + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s date=%s", + "set_cookie", cookie_string, + URL_HOST_(set_url), path ? path : "/", date); + else + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s", + "set_cookie", cookie_string, + URL_HOST_(set_url), path ? path : "/"); _MSG("Cookies.c: a_Cookies_set \n\t \"%s\" \n",cmd ); /* This call is commented because it doesn't guarantee the order @@ -178,7 +181,7 @@ void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) */ char *a_Cookies_get_query(const DilloUrl *request_url) { - char *cmd, *dpip_tag, *query, numstr[16]; + char *cmd, *dpip_tag, *query; const char *path; CookieControlAction action; @@ -192,10 +195,9 @@ char *a_Cookies_get_query(const DilloUrl *request_url) } path = URL_PATH_(request_url); - snprintf(numstr, 16, "%d", URL_PORT(request_url)); - cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s port=%s", + cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s", "get_cookie", URL_SCHEME(request_url), - URL_HOST(request_url), path ? path : "/", numstr); + URL_HOST(request_url), path ? path : "/"); /* Get the answer from cookies.dpi */ _MSG("cookies.c: a_Dpi_send_blocking_cmd cmd = {%s}\n", cmd); @@ -203,15 +205,11 @@ char *a_Cookies_get_query(const DilloUrl *request_url) _MSG("cookies.c: after a_Dpi_send_blocking_cmd resp={%s}\n", dpip_tag); dFree(cmd); - query = dStrdup("Cookie2: $Version=\"1\"\r\n"); - if (dpip_tag != NULL) { - char *cookie = a_Dpip_get_attr(dpip_tag, "cookie"); - char *old_query = query; - query = dStrconcat(old_query, cookie, NULL); - dFree(old_query); + query = a_Dpip_get_attr(dpip_tag, "cookie"); dFree(dpip_tag); - dFree(cookie); + } else { + query = dStrdup(""); } return query; } diff --git a/src/cookies.h b/src/cookies.h index 6f9f77e0..482aa5ae 100644 --- a/src/cookies.h +++ b/src/cookies.h @@ -12,7 +12,8 @@ extern "C" { # define a_Cookies_freeall() ; #else char *a_Cookies_get_query(const DilloUrl *request_url); - void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url); + void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url, + const char *server_date); void a_Cookies_init( void ); void a_Cookies_freeall( void ); #endif @@ -584,7 +584,13 @@ void CssContext::buildUserAgentStyle () { "td, th {border-style: inset; padding: 2px}" "thead, tbody, tfoot {vertical-align: middle}" "th {font-weight: bolder; text-align: center}" - "code, tt, pre, samp, kbd {font-family: monospace}"; + "code, tt, pre, samp, kbd {font-family: monospace}" + /* WORKAROUND: Reset font properties in tables as some + * some pages rely on it (e.g. gmail). + * http://developer.mozilla.org/En/Fixing_Table_Inheritance_in_Quirks_Mode + * has a detailed description of the issue. + */ + "table, caption {font-size: medium; font-weight: normal}"; CssParser::parse (NULL, NULL, this, cssBuf, strlen (cssBuf), CSS_ORIGIN_USER_AGENT); diff --git a/src/doctree.hh b/src/doctree.hh index 30762e44..ef7faa7c 100644 --- a/src/doctree.hh +++ b/src/doctree.hh @@ -5,12 +5,20 @@ class DoctreeNode { public: + DoctreeNode *parent; int num; // unique ascending id - int depth; int element; lout::misc::SimpleVector<char*> *klass; const char *pseudo; const char *id; + + DoctreeNode () { + parent = NULL; + klass = NULL; + pseudo = NULL; + id = NULL; + element = 0; + }; }; /** @@ -23,10 +31,42 @@ class DoctreeNode { * be extended to a real tree. */ class Doctree { + private: + DoctreeNode *topNode; + int num; + public: - virtual ~Doctree () {}; - virtual const DoctreeNode *top () = 0; - virtual const DoctreeNode *parent (const DoctreeNode *node) = 0; + Doctree () { + topNode = NULL; + num = 0; + }; + ~Doctree () { while (top ()) pop (); }; + DoctreeNode *push () { + DoctreeNode *dn = new DoctreeNode (); + dn->parent = topNode; + dn->num = num++; + topNode = dn; + return dn; + }; + void pop () { + DoctreeNode *dn = topNode; + if (dn) { + dFree ((void*) dn->id); + if (dn->klass) { + for (int i = 0; i < dn->klass->size (); i++) + dFree (dn->klass->get(i)); + delete dn->klass; + } + topNode = dn->parent; + delete dn; + } + }; + inline DoctreeNode *top () { + return topNode; + }; + inline DoctreeNode *parent (const DoctreeNode *node) { + return node->parent; + }; }; #endif diff --git a/src/styleengine.cc b/src/styleengine.cc index 1ca57054..f0f70bf6 100644 --- a/src/styleengine.cc +++ b/src/styleengine.cc @@ -21,14 +21,14 @@ StyleEngine::StyleEngine (dw::core::Layout *layout) { StyleAttrs style_attrs; FontAttrs font_attrs; + doctree = new Doctree (); stack = new lout::misc::SimpleVector <Node> (1); cssContext = new CssContext (); this->layout = layout; - num = 0; importDepth = 0; stack->increase (); - Node *n = stack->getRef (stack->size () - 1); + Node *n = stack->getRef (stack->size () - 1); /* Create a dummy font, attribute, and tag for the bottom of the stack. */ font_attrs.name = prefs.font_sans_serif; @@ -46,21 +46,17 @@ StyleEngine::StyleEngine (dw::core::Layout *layout) { style_attrs.color = Color::create (layout, 0); style_attrs.backgroundColor = Color::create (layout, 0xffffff); - n->num = num++; n->style = Style::create (layout, &style_attrs); n->wordStyle = NULL; - n->element = 0; - n->id = NULL; - n->klass = NULL; - n->pseudo = NULL; n->styleAttribute = NULL; n->inheritBackgroundColor = false; } StyleEngine::~StyleEngine () { - while (stack->size () > 0) - endElement (stack->getRef (stack->size () - 1)->element); + while (doctree->top ()) + endElement (doctree->top ()->element); delete stack; + delete doctree; delete cssContext; } @@ -72,17 +68,14 @@ void StyleEngine::startElement (int element) { style0 (); stack->increase (); - Node *n = stack->getRef (stack->size () - 1); - n->num = num++; + Node *n = stack->getRef (stack->size () - 1); n->style = NULL; n->wordStyle = NULL; - n->depth = stack->size () - 1; - n->element = element; - n->id = NULL; - n->klass = NULL; - n->pseudo = NULL; n->styleAttribute = NULL; n->inheritBackgroundColor = false; + + DoctreeNode *dn = doctree->push (); + dn->element = element; } void StyleEngine::startElement (const char *tagname) { @@ -90,9 +83,9 @@ void StyleEngine::startElement (const char *tagname) { } void StyleEngine::setId (const char *id) { - Node *n = stack->getRef (stack->size () - 1); - assert (n->id == NULL); - n->id = dStrdup (id); + DoctreeNode *dn = doctree->top (); + assert (dn->id == NULL); + dn->id = dStrdup (id); }; /** @@ -121,13 +114,13 @@ static lout::misc::SimpleVector<char *> *splitStr (const char *str, char sep) { } void StyleEngine::setClass (const char *klass) { - Node *n = stack->getRef (stack->size () - 1); - assert (n->klass == NULL); - n->klass = splitStr (klass, ' '); + DoctreeNode *dn = doctree->top (); + assert (dn->klass == NULL); + dn->klass = splitStr (klass, ' '); }; void StyleEngine::setStyle (const char *style) { - Node *n = stack->getRef (stack->size () - 1); + Node *n = stack->getRef (stack->size () - 1); assert (n->styleAttribute == NULL); n->styleAttribute = dStrdup (style); }; @@ -156,16 +149,16 @@ void StyleEngine::inheritBackgroundColor () { * \brief set the CSS pseudo class :link. */ void StyleEngine::setPseudoLink () { - Node *n = stack->getRef (stack->size () - 1); - n->pseudo = "link"; + DoctreeNode *dn = doctree->top (); + dn->pseudo = "link"; } /** * \brief set the CSS pseudo class :visited. */ void StyleEngine::setPseudoVisited () { - Node *n = stack->getRef (stack->size () - 1); - n->pseudo = "visited"; + DoctreeNode *dn = doctree->top (); + dn->pseudo = "visited"; } /** @@ -173,24 +166,18 @@ void StyleEngine::setPseudoVisited () { */ void StyleEngine::endElement (int element) { assert (stack->size () > 0); - assert (element == stack->getRef (stack->size () - 1)->element); + assert (element == doctree->top ()->element); - Node *n = stack->getRef (stack->size () - 1); + Node *n = stack->getRef (stack->size () - 1); if (n->style) n->style->unref (); if (n->wordStyle) n->wordStyle->unref (); - if (n->id) - dFree ((void*) n->id); - if (n->klass) { - for (int i = 0; i < n->klass->size (); i++) - dFree (n->klass->get(i)); - delete n->klass; - } if (n->styleAttribute) dFree ((void*) n->styleAttribute); + doctree->pop (); stack->setSize (stack->size () - 1); } @@ -622,7 +609,7 @@ Style * StyleEngine::style0 (CssPropertyList *nonCssProperties) { strlen (styleAttribute)); // merge style information - cssContext->apply (&props, this, styleAttributeProps, nonCssProperties); + cssContext->apply (&props, doctree, styleAttributeProps, nonCssProperties); // apply style apply (&attrs, &props); diff --git a/src/styleengine.hh b/src/styleengine.hh index 8b5dd1fd..66f28cee 100644 --- a/src/styleengine.hh +++ b/src/styleengine.hh @@ -17,9 +17,9 @@ class StyleEngine; * HTML elements and their attributes via the startElement() / endElement() * methods. */ -class StyleEngine : public Doctree { +class StyleEngine { private: - class Node : public DoctreeNode { + class Node { public: dw::core::style::Style *style; dw::core::style::Style *wordStyle; @@ -30,7 +30,7 @@ class StyleEngine : public Doctree { dw::core::Layout *layout; lout::misc::SimpleVector <Node> *stack; CssContext *cssContext; - int num; + Doctree *doctree; int importDepth; dw::core::style::Style *style0 (CssPropertyList *nonCssHints = NULL); @@ -49,24 +49,12 @@ class StyleEngine : public Doctree { StyleEngine (dw::core::Layout *layout); ~StyleEngine (); - /* Doctree interface */ - inline const DoctreeNode *top () { - return stack->getRef (stack->size () - 1); - }; - - inline const DoctreeNode *parent (const DoctreeNode *n) { - if (n->depth > 1) - return stack->getRef (n->depth - 1); - else - return NULL; - }; - void parse (DilloHtml *html, DilloUrl *url, const char *buf, int buflen, CssOrigin origin); void startElement (int tag); void startElement (const char *tagname); void setId (const char *id); - const char * getId () { return top ()->id; }; + const char * getId () { return doctree->top ()->id; }; void setClass (const char *klass); void setStyle (const char *style); void endElement (int tag); |