1 |
/* |
/* |
2 |
* HTTP Header Lint |
* HTTP Header Lint |
3 |
* Licensed under the same license as Curl |
* Licensed under the MIT License |
4 |
* http://curl.haxx.se/docs/copyright.html |
* http://www.opensource.org/licenses/mit-license |
5 |
* Copyright 2003 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2004 James Bursa <bursa@users.sourceforge.net> |
6 |
*/ |
*/ |
7 |
|
|
8 |
/* |
/* |
36 |
int status_code; |
int status_code; |
37 |
char error_buffer[CURL_ERROR_SIZE]; |
char error_buffer[CURL_ERROR_SIZE]; |
38 |
regex_t re_status_line, re_token, re_token_value, re_content_type, re_ugly, |
regex_t re_status_line, re_token, re_token_value, re_content_type, re_ugly, |
39 |
re_absolute_uri, re_etag, re_server, re_transfer_coding, re_upgrade; |
re_absolute_uri, re_etag, re_server, re_transfer_coding, re_upgrade, |
40 |
|
re_rfc1123, re_rfc1036, re_asctime, re_cookie_nameval, re_cookie_expires; |
41 |
|
|
42 |
|
|
43 |
void init(void); |
void init(void); |
48 |
void check_status_line(const char *s); |
void check_status_line(const char *s); |
49 |
void check_header(const char *name, const char *value); |
void check_header(const char *name, const char *value); |
50 |
bool parse_date(const char *s, struct tm *tm); |
bool parse_date(const char *s, struct tm *tm); |
51 |
|
int month(const char *s); |
52 |
|
time_t mktime_from_utc(struct tm *t); |
53 |
const char *skip_lws(const char *s); |
const char *skip_lws(const char *s); |
54 |
bool parse_list(const char *s, regex_t *preg, unsigned int n, unsigned int m, |
bool parse_list(const char *s, regex_t *preg, unsigned int n, unsigned int m, |
55 |
void (*callback)(const char *s, regmatch_t pmatch[])); |
void (*callback)(const char *s, regmatch_t pmatch[])); |
81 |
void header_upgrade(const char *s); |
void header_upgrade(const char *s); |
82 |
void header_vary(const char *s); |
void header_vary(const char *s); |
83 |
void header_via(const char *s); |
void header_via(const char *s); |
84 |
|
void header_set_cookie(const char *s); |
85 |
void die(const char *error); |
void die(const char *error); |
86 |
void warning(const char *message); |
void warning(const char *message); |
87 |
void error(const char *message); |
void error(const char *message); |
115 |
{ "Pragma", header_pragma, 0, 0 }, |
{ "Pragma", header_pragma, 0, 0 }, |
116 |
{ "Retry-After", header_retry_after, 0, 0 }, |
{ "Retry-After", header_retry_after, 0, 0 }, |
117 |
{ "Server", header_server, 0, 0 }, |
{ "Server", header_server, 0, 0 }, |
118 |
|
{ "Set-Cookie", header_set_cookie, 0, 0 }, |
119 |
{ "Trailer", header_trailer, 0, 0 }, |
{ "Trailer", header_trailer, 0, 0 }, |
120 |
{ "Transfer-Encoding", header_transfer_encoding, 0, 0 }, |
{ "Transfer-Encoding", header_transfer_encoding, 0, 0 }, |
121 |
{ "Upgrade", header_upgrade, 0, 0 }, |
{ "Upgrade", header_upgrade, 0, 0 }, |
179 |
"^HTTP/([0-9]+)[.]([0-9]+) ([0-9][0-9][0-9]) ([\t -~€-ÿ]*)$", |
"^HTTP/([0-9]+)[.]([0-9]+) ([0-9][0-9][0-9]) ([\t -~€-ÿ]*)$", |
180 |
REG_EXTENDED); |
REG_EXTENDED); |
181 |
regcomp_wrapper(&re_token, |
regcomp_wrapper(&re_token, |
182 |
"^([-0-9a-zA-Z_.]+)", |
"^([-0-9a-zA-Z_.!]+)", |
183 |
REG_EXTENDED); |
REG_EXTENDED); |
184 |
regcomp_wrapper(&re_token_value, |
regcomp_wrapper(&re_token_value, |
185 |
"^([-0-9a-zA-Z_.]+)(=([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\"))?", |
"^([-0-9a-zA-Z_.!]+)(=([-0-9a-zA-Z_.!]+|\"([^\"]|[\\].)*\"))?", |
186 |
REG_EXTENDED); |
REG_EXTENDED); |
187 |
regcomp_wrapper(&re_content_type, |
regcomp_wrapper(&re_content_type, |
188 |
"^([-0-9a-zA-Z_.]+)/([-0-9a-zA-Z_.]+)[ \t]*" |
"^([-0-9a-zA-Z_.]+)/([-0-9a-zA-Z_.]+)[ \t]*" |
196 |
"^(W/[ \t]*)?\"([^\"]|[\\].)*\"$", |
"^(W/[ \t]*)?\"([^\"]|[\\].)*\"$", |
197 |
REG_EXTENDED); |
REG_EXTENDED); |
198 |
regcomp_wrapper(&re_server, |
regcomp_wrapper(&re_server, |
199 |
"^((([-0-9a-zA-Z_.]+(/[-0-9a-zA-Z_.]+)?)|(\\(.*\\)))[ \t]*)+$", |
"^((([-0-9a-zA-Z_.!]+(/[-0-9a-zA-Z_.]+)?)|(\\(.*\\)))[ \t]*)+$", |
200 |
REG_EXTENDED); |
REG_EXTENDED); |
201 |
regcomp_wrapper(&re_transfer_coding, |
regcomp_wrapper(&re_transfer_coding, |
202 |
"^([-0-9a-zA-Z_.]+)[ \t]*" |
"^([-0-9a-zA-Z_.]+)[ \t]*" |
207 |
"^([-0-9a-zA-Z_.](/[-0-9a-zA-Z_.])?)+$", |
"^([-0-9a-zA-Z_.](/[-0-9a-zA-Z_.])?)+$", |
208 |
REG_EXTENDED); |
REG_EXTENDED); |
209 |
regcomp_wrapper(&re_ugly, |
regcomp_wrapper(&re_ugly, |
210 |
"^[a-zA-Z0-9]+://[^/]+[/a-zA-Z0-9-_]*$", |
"^[a-zA-Z0-9]+://[^/]+[-/a-zA-Z0-9_]*$", |
211 |
|
REG_EXTENDED); |
212 |
|
regcomp_wrapper(&re_rfc1123, |
213 |
|
"^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0123][0-9]) " |
214 |
|
"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) " |
215 |
|
"([012][0-9]):([0-5][0-9]):([0-5][0-9]) GMT$", |
216 |
|
REG_EXTENDED); |
217 |
|
regcomp_wrapper(&re_rfc1036, |
218 |
|
"^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), " |
219 |
|
"([0123][0-9])-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-" |
220 |
|
"([0-9][0-9]) ([012][0-9]):([0-5][0-9]):([0-5][0-9]) GMT$", |
221 |
|
REG_EXTENDED); |
222 |
|
regcomp_wrapper(&re_asctime, |
223 |
|
"^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) " |
224 |
|
"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([ 12][0-9]) " |
225 |
|
"([012][0-9]):([0-5][0-9]):([0-5][0-9]) ([0-9]{4})$", |
226 |
|
REG_EXTENDED); |
227 |
|
regcomp_wrapper(&re_cookie_nameval, |
228 |
|
"^[^;, ]+=[^;, ]*$", |
229 |
|
REG_EXTENDED); |
230 |
|
regcomp_wrapper(&re_cookie_expires, |
231 |
|
"^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0123][0-9])-" |
232 |
|
"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-([0-9]{4}) " |
233 |
|
"([012][0-9]):([0-5][0-9]):([0-5][0-9]) GMT$", |
234 |
REG_EXTENDED); |
REG_EXTENDED); |
235 |
} |
} |
236 |
|
|
408 |
if (header) { |
if (header) { |
409 |
header->count++; |
header->count++; |
410 |
header->handler(value); |
header->handler(value); |
411 |
} else |
} else if ((name[0] == 'X' || name[0] == 'x') && name[1] == '-') { |
412 |
|
lookup("xheader"); |
413 |
|
} else { |
414 |
lookup("nonstandard"); |
lookup("nonstandard"); |
415 |
|
} |
416 |
} |
} |
417 |
|
|
418 |
|
|
421 |
*/ |
*/ |
422 |
bool parse_date(const char *s, struct tm *tm) |
bool parse_date(const char *s, struct tm *tm) |
423 |
{ |
{ |
424 |
char *r; |
int r; |
425 |
int len = strlen(s); |
int len = strlen(s); |
426 |
|
regmatch_t pmatch[20]; |
427 |
|
|
428 |
if (len == 29) { |
if (len == 29) { |
429 |
/* RFC 1123 */ |
/* RFC 1123 */ |
430 |
r = strptime(s, "%a, %d %b %Y %H:%M:%S GMT", tm); |
r = regexec(&re_rfc1123, s, 20, pmatch, 0); |
431 |
if (r == s + len) |
if (r == 0) { |
432 |
|
tm->tm_mday = atoi(s + pmatch[2].rm_so); |
433 |
|
tm->tm_mon = month(s + pmatch[3].rm_so); |
434 |
|
tm->tm_year = atoi(s + pmatch[4].rm_so) - 1900; |
435 |
|
tm->tm_hour = atoi(s + pmatch[5].rm_so); |
436 |
|
tm->tm_min = atoi(s + pmatch[6].rm_so); |
437 |
|
tm->tm_sec = atoi(s + pmatch[7].rm_so); |
438 |
return true; |
return true; |
439 |
|
} |
440 |
|
|
441 |
} else if (len == 24) { |
} else if (len == 24) { |
442 |
/* asctime() format */ |
/* asctime() format */ |
443 |
r = strptime(s, "%a %b %d %H:%M:%S %Y", tm); |
r = regexec(&re_asctime, s, 20, pmatch, 0); |
444 |
if (r == s + len) { |
if (r == 0) { |
445 |
lookup("asctime"); |
if (s[pmatch[3].rm_so] == ' ') |
446 |
return true; |
tm->tm_mday = atoi(s + pmatch[3].rm_so + 1); |
447 |
} |
else |
448 |
r = strptime(s, "%a %b %d %H:%M:%S %Y", tm); |
tm->tm_mday = atoi(s + pmatch[3].rm_so); |
449 |
if (r == s + len) { |
tm->tm_mon = month(s + pmatch[2].rm_so); |
450 |
|
tm->tm_year = atoi(s + pmatch[7].rm_so) - 1900; |
451 |
|
tm->tm_hour = atoi(s + pmatch[4].rm_so); |
452 |
|
tm->tm_min = atoi(s + pmatch[5].rm_so); |
453 |
|
tm->tm_sec = atoi(s + pmatch[6].rm_so); |
454 |
lookup("asctime"); |
lookup("asctime"); |
455 |
return true; |
return true; |
456 |
} |
} |
457 |
|
|
458 |
} else { |
} else { |
459 |
/* RFC 1036 */ |
/* RFC 1036 */ |
460 |
r = strptime(s, "%a, %d-%b-%y %H:%M:%S GMT", tm); |
r = regexec(&re_rfc1036, s, 20, pmatch, 0); |
461 |
if (r == s + len) { |
if (r == 0) { |
462 |
|
tm->tm_mday = atoi(s + pmatch[2].rm_so); |
463 |
|
tm->tm_mon = month(s + pmatch[3].rm_so); |
464 |
|
tm->tm_year = 100 + atoi(s + pmatch[4].rm_so); |
465 |
|
tm->tm_hour = atoi(s + pmatch[5].rm_so); |
466 |
|
tm->tm_min = atoi(s + pmatch[6].rm_so); |
467 |
|
tm->tm_sec = atoi(s + pmatch[7].rm_so); |
468 |
lookup("rfc1036"); |
lookup("rfc1036"); |
469 |
return true; |
return true; |
470 |
} |
} |
477 |
|
|
478 |
|
|
479 |
/** |
/** |
480 |
|
* Convert a month name to the month number. |
481 |
|
*/ |
482 |
|
int month(const char *s) |
483 |
|
{ |
484 |
|
switch (s[0]) { |
485 |
|
case 'J': |
486 |
|
switch (s[1]) { |
487 |
|
case 'a': |
488 |
|
return 0; |
489 |
|
case 'u': |
490 |
|
return s[2] == 'n' ? 5 : 6; |
491 |
|
} |
492 |
|
case 'F': |
493 |
|
return 1; |
494 |
|
case 'M': |
495 |
|
return s[2] == 'r' ? 2 : 4; |
496 |
|
case 'A': |
497 |
|
return s[1] == 'p' ? 3 : 7; |
498 |
|
case 'S': |
499 |
|
return 8; |
500 |
|
case 'O': |
501 |
|
return 9; |
502 |
|
case 'N': |
503 |
|
return 10; |
504 |
|
case 'D': |
505 |
|
return 11; |
506 |
|
} |
507 |
|
return 0; |
508 |
|
} |
509 |
|
|
510 |
|
|
511 |
|
/** |
512 |
|
* UTC version of mktime, from |
513 |
|
* http://lists.debian.org/deity/2002/deity-200204/msg00082.html |
514 |
|
*/ |
515 |
|
time_t mktime_from_utc(struct tm *t) |
516 |
|
{ |
517 |
|
time_t tl, tb; |
518 |
|
struct tm *tg; |
519 |
|
|
520 |
|
tl = mktime (t); |
521 |
|
if (tl == -1) |
522 |
|
{ |
523 |
|
t->tm_hour--; |
524 |
|
tl = mktime (t); |
525 |
|
if (tl == -1) |
526 |
|
return -1; /* can't deal with output from strptime */ |
527 |
|
tl += 3600; |
528 |
|
} |
529 |
|
tg = gmtime (&tl); |
530 |
|
tg->tm_isdst = 0; |
531 |
|
tb = mktime (tg); |
532 |
|
if (tb == -1) |
533 |
|
{ |
534 |
|
tg->tm_hour--; |
535 |
|
tb = mktime (tg); |
536 |
|
if (tb == -1) |
537 |
|
return -1; /* can't deal with output from gmtime */ |
538 |
|
tb += 3600; |
539 |
|
} |
540 |
|
return (tl - (tb - tl)); |
541 |
|
} |
542 |
|
|
543 |
|
|
544 |
|
/** |
545 |
* Skip optional LWS (linear white space) [2.2] |
* Skip optional LWS (linear white space) [2.2] |
546 |
*/ |
*/ |
547 |
const char *skip_lws(const char *s) |
const char *skip_lws(const char *s) |
795 |
time0 = time(0); |
time0 = time(0); |
796 |
if (!parse_date(s, &tm)) |
if (!parse_date(s, &tm)) |
797 |
return; |
return; |
798 |
time1 = mktime(&tm); |
time1 = mktime_from_utc(&tm); |
799 |
|
|
800 |
diff = difftime(time0, time1); |
diff = difftime(time0, time1); |
801 |
if (10 < fabs(diff)) |
if (10 < fabs(diff)) |
830 |
time0 = time(0); |
time0 = time(0); |
831 |
if (!parse_date(s, &tm)) |
if (!parse_date(s, &tm)) |
832 |
return; |
return; |
833 |
time1 = mktime(&tm); |
time1 = mktime_from_utc(&tm); |
834 |
|
|
835 |
diff = difftime(time1, time0); |
diff = difftime(time1, time0); |
836 |
if (10 < diff) |
if (10 < diff) |
951 |
lookup("via"); |
lookup("via"); |
952 |
} |
} |
953 |
|
|
954 |
|
/* http://wp.netscape.com/newsref/std/cookie_spec.html */ |
955 |
|
void header_set_cookie(const char *s) |
956 |
|
{ |
957 |
|
bool ok = true; |
958 |
|
int r; |
959 |
|
const char *semi = strchr(s, ';'); |
960 |
|
const char *s2; |
961 |
|
struct tm tm; |
962 |
|
double diff; |
963 |
|
time_t time0, time1; |
964 |
|
regmatch_t pmatch[20]; |
965 |
|
|
966 |
|
if (semi) |
967 |
|
s2 = strndup(s, semi - s); |
968 |
|
else |
969 |
|
s2 = s; |
970 |
|
|
971 |
|
r = regexec(&re_cookie_nameval, s2, 0, 0, 0); |
972 |
|
if (r) { |
973 |
|
lookup("cookiebadnameval"); |
974 |
|
ok = false; |
975 |
|
} |
976 |
|
|
977 |
|
if (!semi) |
978 |
|
return; |
979 |
|
|
980 |
|
s = skip_lws(semi + 1); |
981 |
|
|
982 |
|
while (*s) { |
983 |
|
semi = strchr(s, ';'); |
984 |
|
if (semi) |
985 |
|
s2 = strndup(s, semi - s); |
986 |
|
else |
987 |
|
s2 = s; |
988 |
|
|
989 |
|
if (strncmp(s2, "expires=", 8) == 0) { |
990 |
|
s2 += 8; |
991 |
|
r = regexec(&re_cookie_expires, s2, 20, pmatch, 0); |
992 |
|
if (r == 0) { |
993 |
|
tm.tm_mday = atoi(s2 + pmatch[2].rm_so); |
994 |
|
tm.tm_mon = month(s2 + pmatch[3].rm_so); |
995 |
|
tm.tm_year = atoi(s2 + pmatch[4].rm_so) - 1900; |
996 |
|
tm.tm_hour = atoi(s2 + pmatch[5].rm_so); |
997 |
|
tm.tm_min = atoi(s2 + pmatch[6].rm_so); |
998 |
|
tm.tm_sec = atoi(s2 + pmatch[7].rm_so); |
999 |
|
|
1000 |
|
time0 = time(0); |
1001 |
|
time1 = mktime_from_utc(&tm); |
1002 |
|
|
1003 |
|
diff = difftime(time0, time1); |
1004 |
|
if (10 < diff) { |
1005 |
|
lookup("cookiepastdate"); |
1006 |
|
ok = false; |
1007 |
|
} |
1008 |
|
} else { |
1009 |
|
lookup("cookiebaddate"); |
1010 |
|
ok = false; |
1011 |
|
} |
1012 |
|
} else if (strncmp(s2, "domain=", 7) == 0) { |
1013 |
|
} else if (strncmp(s2, "path=", 5) == 0) { |
1014 |
|
if (s2[5] != '/') { |
1015 |
|
lookup("cookiebadpath"); |
1016 |
|
ok = false; |
1017 |
|
} |
1018 |
|
} else if (strcmp(s, "secure") == 0) { |
1019 |
|
} else { |
1020 |
|
printf(" Set-Cookie field '%s':\n", s2); |
1021 |
|
lookup("cookieunknownfield"); |
1022 |
|
ok = false; |
1023 |
|
} |
1024 |
|
|
1025 |
|
if (semi) |
1026 |
|
s = skip_lws(semi + 1); |
1027 |
|
else |
1028 |
|
break; |
1029 |
|
} |
1030 |
|
|
1031 |
|
if (ok) |
1032 |
|
lookup("ok"); |
1033 |
|
} |
1034 |
|
|
1035 |
|
|
1036 |
/** |
/** |
1037 |
* Print an error message and exit. |
* Print an error message and exit. |
1136 |
"of header names, or \"*\"." }, |
"of header names, or \"*\"." }, |
1137 |
{ "contentrange", "Warning: The Content-Range header should not be returned " |
{ "contentrange", "Warning: The Content-Range header should not be returned " |
1138 |
"by the server for this request." }, |
"by the server for this request." }, |
1139 |
|
{ "cookiebaddate", "Error: The expires date must be in the form " |
1140 |
|
"\"Wdy, DD-Mon-YYYY HH:MM:SS GMT\"." }, |
1141 |
|
{ "cookiebadnameval", "Error: A Set-Cookie header must start with " |
1142 |
|
"name=value, each excluding semi-colon, comma and " |
1143 |
|
"white space." }, |
1144 |
|
{ "cookiebadpath", "Error: The path does not start with \"/\"." }, |
1145 |
|
{ "cookiepastdate", "Warning: The expires date is in the past. The cookie " |
1146 |
|
"will be deleted by browsers." }, |
1147 |
|
{ "cookieunknownfield", "Warning: This is not a standard Set-Cookie " |
1148 |
|
"field." }, |
1149 |
{ "futurehttp", "Warning: I only understand HTTP/1.1. Check for a newer " |
{ "futurehttp", "Warning: I only understand HTTP/1.1. Check for a newer " |
1150 |
"version of this tool." }, |
"version of this tool." }, |
1151 |
{ "futurelastmod", "Error: The specified Last-Modified date-time is in " |
{ "futurelastmod", "Error: The specified Last-Modified date-time is in " |
1190 |
{ "via", "This header was added by a proxy, cache or gateway." }, |
{ "via", "This header was added by a proxy, cache or gateway." }, |
1191 |
{ "wrongdate", "Warning: The server date-time differs from this system's " |
{ "wrongdate", "Warning: The server date-time differs from this system's " |
1192 |
"date-time by more than 10 seconds. Check that both the " |
"date-time by more than 10 seconds. Check that both the " |
1193 |
"system clocks are correct." } |
"system clocks are correct." }, |
1194 |
|
{ "xheader", "This is an extension header. I don't know how to check it." } |
1195 |
}; |
}; |
1196 |
|
|
1197 |
|
|