--- httplint/httplint.c 2003/12/15 15:07:31 40 +++ httplint/httplint.c 2004/02/20 20:13:07 48 @@ -36,7 +36,8 @@ int status_code; char error_buffer[CURL_ERROR_SIZE]; regex_t re_status_line, re_token, re_token_value, re_content_type, re_ugly, - re_absolute_uri, re_etag, re_server, re_transfer_coding, re_upgrade; + re_absolute_uri, re_etag, re_server, re_transfer_coding, re_upgrade, + re_rfc1123, re_rfc1036, re_asctime, re_cookie_nameval, re_cookie_expires; void init(void); @@ -47,6 +48,8 @@ void check_status_line(const char *s); void check_header(const char *name, const char *value); bool parse_date(const char *s, struct tm *tm); +int month(const char *s); +time_t mktime_from_utc(struct tm *t); const char *skip_lws(const char *s); bool parse_list(const char *s, regex_t *preg, unsigned int n, unsigned int m, void (*callback)(const char *s, regmatch_t pmatch[])); @@ -78,6 +81,7 @@ void header_upgrade(const char *s); void header_vary(const char *s); void header_via(const char *s); +void header_set_cookie(const char *s); void die(const char *error); void warning(const char *message); void error(const char *message); @@ -111,6 +115,7 @@ { "Pragma", header_pragma, 0, 0 }, { "Retry-After", header_retry_after, 0, 0 }, { "Server", header_server, 0, 0 }, + { "Set-Cookie", header_set_cookie, 0, 0 }, { "Trailer", header_trailer, 0, 0 }, { "Transfer-Encoding", header_transfer_encoding, 0, 0 }, { "Upgrade", header_upgrade, 0, 0 }, @@ -174,10 +179,10 @@ "^HTTP/([0-9]+)[.]([0-9]+) ([0-9][0-9][0-9]) ([\t -~€-ÿ]*)$", REG_EXTENDED); regcomp_wrapper(&re_token, - "^([-0-9a-zA-Z_.]+)", + "^([-0-9a-zA-Z_.!]+)", REG_EXTENDED); regcomp_wrapper(&re_token_value, - "^([-0-9a-zA-Z_.]+)(=([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\"))?", + "^([-0-9a-zA-Z_.!]+)(=([-0-9a-zA-Z_.!]+|\"([^\"]|[\\].)*\"))?", REG_EXTENDED); regcomp_wrapper(&re_content_type, "^([-0-9a-zA-Z_.]+)/([-0-9a-zA-Z_.]+)[ \t]*" @@ -191,7 +196,7 @@ "^(W/[ \t]*)?\"([^\"]|[\\].)*\"$", REG_EXTENDED); regcomp_wrapper(&re_server, - "^((([-0-9a-zA-Z_.]+(/[-0-9a-zA-Z_.]+)?)|(\\(.*\\)))[ \t]*)+$", + "^((([-0-9a-zA-Z_.!]+(/[-0-9a-zA-Z_.]+)?)|(\\(.*\\)))[ \t]*)+$", REG_EXTENDED); regcomp_wrapper(&re_transfer_coding, "^([-0-9a-zA-Z_.]+)[ \t]*" @@ -202,7 +207,30 @@ "^([-0-9a-zA-Z_.](/[-0-9a-zA-Z_.])?)+$", REG_EXTENDED); regcomp_wrapper(&re_ugly, - "^[a-zA-Z0-9]+://[^/]+[/a-zA-Z0-9-_]*$", + "^[a-zA-Z0-9]+://[^/]+[-/a-zA-Z0-9_]*$", + REG_EXTENDED); + regcomp_wrapper(&re_rfc1123, + "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0123][0-9]) " + "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) " + "([012][0-9]):([0-5][0-9]):([0-5][0-9]) GMT$", + REG_EXTENDED); + regcomp_wrapper(&re_rfc1036, + "^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), " + "([0123][0-9])-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-" + "([0-9][0-9]) ([012][0-9]):([0-5][0-9]):([0-5][0-9]) GMT$", + REG_EXTENDED); + regcomp_wrapper(&re_asctime, + "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) " + "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([ 12][0-9]) " + "([012][0-9]):([0-5][0-9]):([0-5][0-9]) ([0-9]{4})$", + REG_EXTENDED); + regcomp_wrapper(&re_cookie_nameval, + "^[^;, ]+=[^;, ]*$", + REG_EXTENDED); + regcomp_wrapper(&re_cookie_expires, + "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0123][0-9])-" + "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-([0-9]{4}) " + "([012][0-9]):([0-5][0-9]):([0-5][0-9]) GMT$", REG_EXTENDED); } @@ -380,8 +408,11 @@ if (header) { header->count++; header->handler(value); - } else + } else if ((name[0] == 'X' || name[0] == 'x') && name[1] == '-') { + lookup("xheader"); + } else { lookup("nonstandard"); + } } @@ -390,32 +421,50 @@ */ bool parse_date(const char *s, struct tm *tm) { - char *r; + int r; int len = strlen(s); + regmatch_t pmatch[20]; if (len == 29) { /* RFC 1123 */ - r = strptime(s, "%a, %d %b %Y %H:%M:%S GMT", tm); - if (r == s + len) + r = regexec(&re_rfc1123, s, 20, pmatch, 0); + if (r == 0) { + tm->tm_mday = atoi(s + pmatch[2].rm_so); + tm->tm_mon = month(s + pmatch[3].rm_so); + tm->tm_year = atoi(s + pmatch[4].rm_so) - 1900; + tm->tm_hour = atoi(s + pmatch[5].rm_so); + tm->tm_min = atoi(s + pmatch[6].rm_so); + tm->tm_sec = atoi(s + pmatch[7].rm_so); return true; + } } else if (len == 24) { /* asctime() format */ - r = strptime(s, "%a %b %d %H:%M:%S %Y", tm); - if (r == s + len) { - lookup("asctime"); - return true; - } - r = strptime(s, "%a %b %d %H:%M:%S %Y", tm); - if (r == s + len) { + r = regexec(&re_asctime, s, 20, pmatch, 0); + if (r == 0) { + if (s[pmatch[3].rm_so] == ' ') + tm->tm_mday = atoi(s + pmatch[3].rm_so + 1); + else + tm->tm_mday = atoi(s + pmatch[3].rm_so); + tm->tm_mon = month(s + pmatch[2].rm_so); + tm->tm_year = atoi(s + pmatch[7].rm_so) - 1900; + tm->tm_hour = atoi(s + pmatch[4].rm_so); + tm->tm_min = atoi(s + pmatch[5].rm_so); + tm->tm_sec = atoi(s + pmatch[6].rm_so); lookup("asctime"); return true; } } else { /* RFC 1036 */ - r = strptime(s, "%a, %d-%b-%y %H:%M:%S GMT", tm); - if (r == s + len) { + r = regexec(&re_rfc1036, s, 20, pmatch, 0); + if (r == 0) { + tm->tm_mday = atoi(s + pmatch[2].rm_so); + tm->tm_mon = month(s + pmatch[3].rm_so); + tm->tm_year = 100 + atoi(s + pmatch[4].rm_so); + tm->tm_hour = atoi(s + pmatch[5].rm_so); + tm->tm_min = atoi(s + pmatch[6].rm_so); + tm->tm_sec = atoi(s + pmatch[7].rm_so); lookup("rfc1036"); return true; } @@ -428,6 +477,71 @@ /** + * Convert a month name to the month number. + */ +int month(const char *s) +{ + switch (s[0]) { + case 'J': + switch (s[1]) { + case 'a': + return 0; + case 'u': + return s[2] == 'n' ? 5 : 6; + } + case 'F': + return 1; + case 'M': + return s[2] == 'r' ? 2 : 4; + case 'A': + return s[1] == 'p' ? 3 : 7; + case 'S': + return 8; + case 'O': + return 9; + case 'N': + return 10; + case 'D': + return 11; + } + return 0; +} + + +/** + * UTC version of mktime, from + * http://lists.debian.org/deity/2002/deity-200204/msg00082.html + */ +time_t mktime_from_utc(struct tm *t) +{ + time_t tl, tb; + struct tm *tg; + + tl = mktime (t); + if (tl == -1) + { + t->tm_hour--; + tl = mktime (t); + if (tl == -1) + return -1; /* can't deal with output from strptime */ + tl += 3600; + } + tg = gmtime (&tl); + tg->tm_isdst = 0; + tb = mktime (tg); + if (tb == -1) + { + tg->tm_hour--; + tb = mktime (tg); + if (tb == -1) + return -1; /* can't deal with output from gmtime */ + tb += 3600; + } + return (tl - (tb - tl)); +} + + +/** * Skip optional LWS (linear white space) [2.2] */ const char *skip_lws(const char *s) @@ -681,7 +795,7 @@ time0 = time(0); if (!parse_date(s, &tm)) return; - time1 = mktime(&tm); + time1 = mktime_from_utc(&tm); diff = difftime(time0, time1); if (10 < fabs(diff)) @@ -716,7 +830,7 @@ time0 = time(0); if (!parse_date(s, &tm)) return; - time1 = mktime(&tm); + time1 = mktime_from_utc(&tm); diff = difftime(time1, time0); if (10 < diff) @@ -837,6 +951,87 @@ lookup("via"); } +/* http://wp.netscape.com/newsref/std/cookie_spec.html */ +void header_set_cookie(const char *s) +{ + bool ok = true; + int r; + const char *semi = strchr(s, ';'); + const char *s2; + struct tm tm; + double diff; + time_t time0, time1; + regmatch_t pmatch[20]; + + if (semi) + s2 = strndup(s, semi - s); + else + s2 = s; + + r = regexec(&re_cookie_nameval, s2, 0, 0, 0); + if (r) { + lookup("cookiebadnameval"); + ok = false; + } + + if (!semi) + return; + + s = skip_lws(semi + 1); + + while (*s) { + semi = strchr(s, ';'); + if (semi) + s2 = strndup(s, semi - s); + else + s2 = s; + + if (strncmp(s2, "expires=", 8) == 0) { + s2 += 8; + r = regexec(&re_cookie_expires, s2, 20, pmatch, 0); + if (r == 0) { + tm.tm_mday = atoi(s2 + pmatch[2].rm_so); + tm.tm_mon = month(s2 + pmatch[3].rm_so); + tm.tm_year = atoi(s2 + pmatch[4].rm_so) - 1900; + tm.tm_hour = atoi(s2 + pmatch[5].rm_so); + tm.tm_min = atoi(s2 + pmatch[6].rm_so); + tm.tm_sec = atoi(s2 + pmatch[7].rm_so); + + time0 = time(0); + time1 = mktime_from_utc(&tm); + + diff = difftime(time0, time1); + if (10 < diff) { + lookup("cookiepastdate"); + ok = false; + } + } else { + lookup("cookiebaddate"); + ok = false; + } + } else if (strncmp(s2, "domain=", 7) == 0) { + } else if (strncmp(s2, "path=", 5) == 0) { + if (s2[5] != '/') { + lookup("cookiebadpath"); + ok = false; + } + } else if (strcmp(s, "secure") == 0) { + } else { + printf(" Set-Cookie field '%s':\n", s2); + lookup("cookieunknownfield"); + ok = false; + } + + if (semi) + s = skip_lws(semi + 1); + else + break; + } + + if (ok) + lookup("ok"); +} + /** * Print an error message and exit. @@ -941,6 +1136,16 @@ "of header names, or \"*\"." }, { "contentrange", "Warning: The Content-Range header should not be returned " "by the server for this request." }, + { "cookiebaddate", "Error: The expires date must be in the form " + "\"Wdy, DD-Mon-YYYY HH:MM:SS GMT\"." }, + { "cookiebadnameval", "Error: A Set-Cookie header must start with " + "name=value, each excluding semi-colon, comma and " + "white space." }, + { "cookiebadpath", "Error: The path does not start with \"/\"." }, + { "cookiepastdate", "Warning: The expires date is in the past. The cookie " + "will be deleted by browsers." }, + { "cookieunknownfield", "Warning: This is not a standard Set-Cookie " + "field." }, { "futurehttp", "Warning: I only understand HTTP/1.1. Check for a newer " "version of this tool." }, { "futurelastmod", "Error: The specified Last-Modified date-time is in " @@ -985,7 +1190,8 @@ { "via", "This header was added by a proxy, cache or gateway." }, { "wrongdate", "Warning: The server date-time differs from this system's " "date-time by more than 10 seconds. Check that both the " - "system clocks are correct." } + "system clocks are correct." }, + { "xheader", "This is an extension header. I don't know how to check it." } };