/* * HTTP Header Lint * Licensed under the same license as Curl * http://curl.haxx.se/docs/copyright.html * Copyright 2003 James Bursa */ /* * Compile using * gcc -W -Wall `curl-config --cflags --libs` -o httplint httplint.c * * References of the form [6.1.1] are to RFC 2616 (HTTP/1.1). */ #define _GNU_SOURCE #define __USE_XOPEN #include #include #include #include #include #include #include #include #include #include #define NUMBER "0123456789" #define UNUSED(x) x = x bool start; CURL *curl; 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_rfc1123, re_rfc1036, re_asctime; void init(void); void regcomp_wrapper(regex_t *preg, const char *regex, int cflags); void check_url(const char *url); size_t header_callback(char *ptr, size_t msize, size_t nmemb, void *stream); size_t data_callback(void *ptr, size_t size, size_t nmemb, void *stream); 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[])); void header_accept_ranges(const char *s); void header_age(const char *s); void header_allow(const char *s); void header_cache_control(const char *s); void header_cache_control_callback(const char *s, regmatch_t pmatch[]); void header_connection(const char *s); void header_content_encoding(const char *s); void header_content_encoding_callback(const char *s, regmatch_t pmatch[]); void header_content_language(const char *s); void header_content_length(const char *s); void header_content_location(const char *s); void header_content_md5(const char *s); void header_content_range(const char *s); void header_content_type(const char *s); void header_date(const char *s); void header_etag(const char *s); void header_expires(const char *s); void header_last_modified(const char *s); void header_location(const char *s); void header_pragma(const char *s); void header_retry_after(const char *s); void header_server(const char *s); void header_trailer(const char *s); void header_transfer_encoding(const char *s); void header_transfer_encoding_callback(const char *s, regmatch_t pmatch[]); void header_upgrade(const char *s); void header_vary(const char *s); void header_via(const char *s); void die(const char *error); void warning(const char *message); void error(const char *message); void print(const char *s, size_t len); void lookup(const char *key); struct header_entry { char name[40]; void (*handler)(const char *s); int count; char *missing; } header_table[] = { { "Accept-Ranges", header_accept_ranges, 0, 0 }, { "Age", header_age, 0, 0 }, { "Allow", header_allow, 0, 0 }, { "Cache-Control", header_cache_control, 0, 0 }, { "Connection", header_connection, 0, 0 }, { "Content-Encoding", header_content_encoding, 0, 0 }, { "Content-Language", header_content_language, 0, "missingcontlang" }, { "Content-Length", header_content_length, 0, 0 }, { "Content-Location", header_content_location, 0, 0 }, { "Content-MD5", header_content_md5, 0, 0 }, { "Content-Range", header_content_range, 0, 0 }, { "Content-Type", header_content_type, 0, "missingcontenttype" }, { "Date", header_date, 0, "missingdate" }, { "ETag", header_etag, 0, 0 }, { "Expires", header_expires, 0, 0 }, { "Last-Modified", header_last_modified, 0, "missinglastmod" }, { "Location", header_location, 0, 0 }, { "Pragma", header_pragma, 0, 0 }, { "Retry-After", header_retry_after, 0, 0 }, { "Server", header_server, 0, 0 }, { "Trailer", header_trailer, 0, 0 }, { "Transfer-Encoding", header_transfer_encoding, 0, 0 }, { "Upgrade", header_upgrade, 0, 0 }, { "Vary", header_vary, 0, 0 }, { "Via", header_via, 0, 0 } }; /** * Main entry point. */ int main(int argc, char *argv[]) { int i; if (argc < 2) die("Usage: httplint url [url ...]"); init(); for (i = 1; i != argc; i++) check_url(argv[i]); curl_global_cleanup(); return 0; } /** * Initialise the curl handle and compile regular expressions. */ void init(void) { struct curl_slist *request_headers = 0; if (curl_global_init(CURL_GLOBAL_ALL)) die("Failed to initialise libcurl"); curl = curl_easy_init(); if (!curl) die("Failed to create curl handle"); if (curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback)) die("Failed to set curl options"); if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, data_callback)) die("Failed to set curl options"); if (curl_easy_setopt(curl, CURLOPT_USERAGENT, "httplint")) die("Failed to set curl options"); if (curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer)) die("Failed to set curl options"); /* remove libcurl default headers */ request_headers = curl_slist_append(request_headers, "Accept:"); request_headers = curl_slist_append(request_headers, "Pragma:"); if (curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers)) die("Failed to set curl options"); /* compile regular expressions */ regcomp_wrapper(&re_status_line, "^HTTP/([0-9]+)[.]([0-9]+) ([0-9][0-9][0-9]) ([\t -~€-ÿ]*)$", REG_EXTENDED); regcomp_wrapper(&re_token, "^([-0-9a-zA-Z_.]+)", REG_EXTENDED); regcomp_wrapper(&re_token_value, "^([-0-9a-zA-Z_.]+)(=([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\"))?", REG_EXTENDED); regcomp_wrapper(&re_content_type, "^([-0-9a-zA-Z_.]+)/([-0-9a-zA-Z_.]+)[ \t]*" "(;[ \t]*([-0-9a-zA-Z_.]+)=" "([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\")[ \t]*)*$", REG_EXTENDED); regcomp_wrapper(&re_absolute_uri, "^[a-zA-Z0-9]+://[^ ]+$", REG_EXTENDED); regcomp_wrapper(&re_etag, "^(W/[ \t]*)?\"([^\"]|[\\].)*\"$", REG_EXTENDED); regcomp_wrapper(&re_server, "^((([-0-9a-zA-Z_.]+(/[-0-9a-zA-Z_.]+)?)|(\\(.*\\)))[ \t]*)+$", REG_EXTENDED); regcomp_wrapper(&re_transfer_coding, "^([-0-9a-zA-Z_.]+)[ \t]*" "(;[ \t]*([-0-9a-zA-Z_.]+)=" "([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\")[ \t]*)*$", REG_EXTENDED); regcomp_wrapper(&re_upgrade, "^([-0-9a-zA-Z_.](/[-0-9a-zA-Z_.])?)+$", REG_EXTENDED); regcomp_wrapper(&re_ugly, "^[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); } /** * Compile a regular expression, handling errors. */ void regcomp_wrapper(regex_t *preg, const char *regex, int cflags) { char errbuf[200]; int r; r = regcomp(preg, regex, cflags); if (r) { regerror(r, preg, errbuf, sizeof errbuf); fprintf(stderr, "Failed to compile regexp '%s'\n", regex); die(errbuf); } } /** * Fetch and check the headers for the specified url. */ void check_url(const char *url) { int i, r; CURLcode code; start = true; for (i = 0; i != sizeof header_table / sizeof header_table[0]; i++) header_table[i].count = 0; printf("Checking URL %s\n", url); if (strncmp(url, "http", 4)) warning("this is not an http or https url"); if (curl_easy_setopt(curl, CURLOPT_URL, url)) die("Failed to set curl options"); code = curl_easy_perform(curl); if (code != CURLE_OK && code != CURLE_WRITE_ERROR) { error(error_buffer); return; } else { printf("\n"); for (i = 0; i != sizeof header_table / sizeof header_table[0]; i++) { if (header_table[i].count == 0 && header_table[i].missing) lookup(header_table[i].missing); } } r = regexec(&re_ugly, url, 0, 0, 0); if (r) lookup("ugly"); } /** * Callback for received header data. */ size_t header_callback(char *ptr, size_t msize, size_t nmemb, void *stream) { const size_t size = msize * nmemb; char s[400], *name, *value; UNUSED(stream); printf("* "); print(ptr, size); printf("\n"); if (size < 2 || ptr[size - 2] != 13 || ptr[size - 1] != 10) { lookup("notcrlf"); return size; } if (sizeof s <= size) { warning("header too long: ignored\n"); return size; } strncpy(s, ptr, size); s[size - 2] = 0; name = s; value = strchr(s, ':'); if (s[0] == 0) { /* empty header indicates end of headers */ puts("End of headers."); return 0; } else if (start) { /* Status-Line [6.1] */ check_status_line(s); start = false; } else if (!value) { lookup("missingcolon"); } else { *value = 0; value++; check_header(name, skip_lws(value)); } return size; } /** * Callback for received body data. * * We are not interested in the body, so abort the fetch by returning 0. */ size_t data_callback(void *ptr, size_t size, size_t nmemb, void *stream) { UNUSED(ptr); UNUSED(size); UNUSED(nmemb); UNUSED(stream); return 0; } /** * Check the syntax and content of the response Status-Line [6.1]. */ void check_status_line(const char *s) { const char *reason; unsigned int major = 0, minor = 0; int r; regmatch_t pmatch[5]; r = regexec(&re_status_line, s, 5, pmatch, 0); if (r) { lookup("badstatusline"); return; } major = atoi(s + pmatch[1].rm_so); minor = atoi(s + pmatch[2].rm_so); status_code = atoi(s + pmatch[3].rm_so); reason = s + pmatch[4].rm_so; if (major < 1 || (major == 1 && minor == 0)) { lookup("oldhttp"); } else if ((major == 1 && 1 < minor) || 1 < major) { lookup("futurehttp"); } else { if (status_code < 100 || 600 <= status_code) { lookup("badstatus"); } else { char key[] = "xxx"; key[0] = '0' + status_code / 100; lookup(key); } } } /** * Check the syntax and content of a header. */ void check_header(const char *name, const char *value) { struct header_entry *header; header = bsearch(name, header_table, sizeof header_table / sizeof header_table[0], sizeof header_table[0], (int (*)(const void *, const void *)) strcasecmp); if (header) { header->count++; header->handler(value); } else lookup("nonstandard"); } /** * Attempt to parse an HTTP Full Date (3.3.1), returning true on success. */ bool parse_date(const char *s, struct tm *tm) { int r; int len = strlen(s); regmatch_t pmatch[20]; if (len == 29) { /* RFC 1123 */ 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 = 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 = 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; } } lookup("baddate"); return false; } /** * 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) { if (s[0] == 13 && s[1] == 10 && (s[2] == ' ' || s[2] == '\t')) s += 2; while (*s == ' ' || *s == '\t') s++; return s; } /** * Parse a list of elements (#rule in [2.1]). */ bool parse_list(const char *s, regex_t *preg, unsigned int n, unsigned int m, void (*callback)(const char *s, regmatch_t pmatch[])) { int r; unsigned int items = 0; regmatch_t pmatch[20]; do { r = regexec(preg, s, 20, pmatch, 0); if (r) { printf(" Failed to match list item %i\n", items + 1); return false; } if (callback) callback(s, pmatch); items++; s += pmatch[0].rm_eo; s = skip_lws(s); if (*s == 0) break; if (*s != ',') { printf(" Expecting , after list item %i\n", items); return false; } while (*s == ',') s = skip_lws(s + 1); } while (*s != 0); if (items < n || m < items) { printf(" %i items in list, but there should be ", items); if (m == UINT_MAX) printf("at least %i\n", n); else printf("between %i and %i\n", n, m); return false; } return true; } /* Header-specific validation. */ void header_accept_ranges(const char *s) { if (strcmp(s, "bytes") == 0) lookup("ok"); else if (strcmp(s, "none") == 0) lookup("ok"); else lookup("unknownrange"); } void header_age(const char *s) { if (s[0] == 0 || strspn(s, NUMBER) != strlen(s)) lookup("badage"); else lookup("ok"); } void header_allow(const char *s) { if (parse_list(s, &re_token, 0, UINT_MAX, 0)) lookup("ok"); else lookup("badallow"); } void header_cache_control(const char *s) { if (parse_list(s, &re_token_value, 1, UINT_MAX, header_cache_control_callback)) lookup("ok"); else lookup("badcachecont"); } char cache_control_list[][20] = { "max-age", "max-stale", "min-fresh", "must-revalidate", "no-cache", "no-store", "no-transform", "only-if-cached", "private", "proxy-revalidate", "public", "s-maxage" }; void header_cache_control_callback(const char *s, regmatch_t pmatch[]) { size_t len = pmatch[1].rm_eo - pmatch[1].rm_so; char name[20]; char *dir; if (19 < len) { lookup("unknowncachecont"); return; } strncpy(name, s + pmatch[1].rm_so, len); name[len] = 0; dir = bsearch(name, cache_control_list, sizeof cache_control_list / sizeof cache_control_list[0], sizeof cache_control_list[0], (int (*)(const void *, const void *)) strcasecmp); if (!dir) { printf(" Cache-Control directive '%s':\n", name); lookup("unknowncachecont"); } } void header_connection(const char *s) { if (strcmp(s, "close") == 0) lookup("ok"); else lookup("badconnection"); } void header_content_encoding(const char *s) { if (parse_list(s, &re_token, 1, UINT_MAX, header_content_encoding_callback)) lookup("ok"); else lookup("badcontenc"); } char content_coding_list[][20] = { "compress", "deflate", "gzip", "identity" }; void header_content_encoding_callback(const char *s, regmatch_t pmatch[]) { size_t len = pmatch[1].rm_eo - pmatch[1].rm_so; char name[20]; char *dir; if (19 < len) { lookup("unknowncontenc"); return; } strncpy(name, s + pmatch[1].rm_so, len); name[len] = 0; dir = bsearch(name, content_coding_list, sizeof content_coding_list / sizeof content_coding_list[0], sizeof content_coding_list[0], (int (*)(const void *, const void *)) strcasecmp); if (!dir) { printf(" Content-Encoding '%s':\n", name); lookup("unknowncontenc"); } } void header_content_language(const char *s) { if (parse_list(s, &re_token, 1, UINT_MAX, 0)) lookup("ok"); else lookup("badcontlang"); } void header_content_length(const char *s) { if (s[0] == 0 || strspn(s, NUMBER) != strlen(s)) lookup("badcontlen"); else lookup("ok"); } void header_content_location(const char *s) { if (strchr(s, ' ')) lookup("badcontloc"); else lookup("ok"); } void header_content_md5(const char *s) { if (strlen(s) != 24) lookup("badcontmd5"); else lookup("ok"); } void header_content_range(const char *s) { UNUSED(s); lookup("contentrange"); } void header_content_type(const char *s) { bool charset = false; char *type, *subtype; unsigned int i; int r; regmatch_t pmatch[30]; r = regexec(&re_content_type, s, 30, pmatch, 0); if (r) { lookup("badcontenttype"); return; } type = strndup(s + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so); subtype = strndup(s + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so); /* parameters */ for (i = 3; i != 30 && pmatch[i].rm_so != -1; i += 3) { char *attrib, *value; attrib = strndup(s + pmatch[i + 1].rm_so, pmatch[i + 1].rm_eo - pmatch[i + 1].rm_so); value = strndup(s + pmatch[i + 2].rm_so, pmatch[i + 2].rm_eo - pmatch[i + 2].rm_so); if (strcasecmp(attrib, "charset") == 0) charset = true; } if (strcasecmp(type, "text") == 0 && !charset) lookup("nocharset"); else lookup("ok"); } void header_date(const char *s) { double diff; time_t time0, time1; struct tm tm; time0 = time(0); if (!parse_date(s, &tm)) return; time1 = mktime_from_utc(&tm); diff = difftime(time0, time1); if (10 < fabs(diff)) lookup("wrongdate"); else lookup("ok"); } void header_etag(const char *s) { int r; r = regexec(&re_etag, s, 0, 0, 0); if (r) lookup("badetag"); else lookup("ok"); } void header_expires(const char *s) { struct tm tm; if (parse_date(s, &tm)) lookup("ok"); } void header_last_modified(const char *s) { double diff; time_t time0, time1; struct tm tm; time0 = time(0); if (!parse_date(s, &tm)) return; time1 = mktime(&tm); diff = difftime(time1, time0); if (10 < diff) lookup("futurelastmod"); else lookup("ok"); } void header_location(const char *s) { int r; r = regexec(&re_absolute_uri, s, 0, 0, 0); if (r) lookup("badlocation"); else lookup("ok"); } void header_pragma(const char *s) { if (parse_list(s, &re_token_value, 1, UINT_MAX, 0)) lookup("ok"); else lookup("badpragma"); } void header_retry_after(const char *s) { struct tm tm; if (s[0] != 0 && strspn(s, NUMBER) == strlen(s)) { lookup("ok"); return; } if (!parse_date(s, &tm)) return; lookup("ok"); } void header_server(const char *s) { int r; r = regexec(&re_server, s, 0, 0, 0); if (r) lookup("badserver"); else lookup("ok"); } void header_trailer(const char *s) { if (parse_list(s, &re_token, 1, UINT_MAX, 0)) lookup("ok"); else lookup("badtrailer"); } void header_transfer_encoding(const char *s) { if (parse_list(s, &re_transfer_coding, 1, UINT_MAX, header_transfer_encoding_callback)) lookup("ok"); else lookup("badtransenc"); } char transfer_coding_list[][20] = { "chunked", "compress", "deflate", "gzip", "identity" }; void header_transfer_encoding_callback(const char *s, regmatch_t pmatch[]) { size_t len = pmatch[1].rm_eo - pmatch[1].rm_so; char name[20]; char *dir; if (19 < len) { lookup("unknowntransenc"); return; } strncpy(name, s + pmatch[1].rm_so, len); name[len] = 0; dir = bsearch(name, transfer_coding_list, sizeof transfer_coding_list / sizeof transfer_coding_list[0], sizeof transfer_coding_list[0], (int (*)(const void *, const void *)) strcasecmp); if (!dir) { printf(" Transfer-Encoding '%s':\n", name); lookup("unknowntransenc"); } } void header_upgrade(const char *s) { int r; r = regexec(&re_upgrade, s, 0, 0, 0); if (r) lookup("badupgrade"); else lookup("ok"); } void header_vary(const char *s) { if (strcmp(s, "*") == 0 || parse_list(s, &re_token, 1, UINT_MAX, 0)) lookup("ok"); else lookup("badvary"); } void header_via(const char *s) { UNUSED(s); lookup("via"); } /** * Print an error message and exit. */ void die(const char *error) { fprintf(stderr, "httplint: %s\n", error); exit(EXIT_FAILURE); } /** * Print a warning message. */ void warning(const char *message) { printf("Warning: %s\n", message); } /** * Print an error message. */ void error(const char *message) { printf("Error: %s\n", message); } /** * Print a string which contains control characters. */ void print(const char *s, size_t len) { size_t i; for (i = 0; i != len; i++) { if (31 < s[i] && s[i] < 127) putchar(s[i]); else printf("[%.2x]", s[i]); } } struct message_entry { const char key[20]; const char *value; } message_table[] = { { "1xx", "A response status code in the range 100 - 199 indicates a " "'provisional response'." }, { "2xx", "A response status code in the range 200 - 299 indicates that " "the request was successful." }, { "3xx", "A response status code in the range 300 - 399 indicates that " "the client should redirect to a new URL." }, { "4xx", "A response status code in the range 400 - 499 indicates that " "the request could not be fulfilled due to client error." }, { "5xx", "A response status code in the range 500 - 599 indicates that " "an error occurred on the server." }, { "asctime", "Warning: This date is in the obsolete asctime() format. " "Consider using the RFC 1123 format instead." }, { "badage", "Error: The Age header must be one number." }, { "badallow", "Error: The Allow header must be a comma-separated list of " "HTTP methods." }, { "badcachecont", "Error: The Cache-Control header must be a " "comma-separated list of directives." }, { "badconnection", "Warning: The only value of the Connection header " "defined by HTTP/1.1 is \"close\"." }, { "badcontenc", "Error: The Content-Encoding header must be a " "comma-separated list of encodings." }, { "badcontenttype", "Error: The Content-Type header must be of the form " "'type/subtype (; optional parameters)'." }, { "badcontlang", "Error: The Content-Language header must be a " "comma-separated list of language tags." }, { "badcontlen", "Error: The Content-Length header must be a number." }, { "badcontloc", "Error: The Content-Location header must be an absolute " "or relative URI." }, { "badcontmd5", "Error: The Content-MD5 header must be a base64 encoded " "MD5 sum." }, { "baddate", "Error: Failed to parse this date. Dates should be in the RFC " "1123 format." }, { "badetag", "Error: The ETag header must be a quoted string (optionally " "preceded by \"W/\" for a weak tag)." }, { "badlocation", "Error: The Location header must be an absolute URI. " "Relative URIs are not permitted." }, { "badpragma", "Error: The Pragma header must be a comma-separated list of " "directives." }, { "badserver", "Error: The Server header must be a space-separated list of " "products of the form Name/optional-version and comments " "in ()." }, { "badstatus", "Warning: The response status code is outside the standard " "range 100 - 599." }, { "badstatusline", "Error: Failed to parse the response Status-Line. The " "status line must be of the form 'HTTP/n.n <3-digit " "status> '." }, { "badtrailer", "Error: The Trailer header must be a comma-separated list " "of header names." }, { "badtransenc", "Error: The Transfer-Encoding header must be a " "comma-separated of encodings." }, { "badupgrade", "Error: The Upgrade header must be a comma-separated list " "of product identifiers." }, { "badvary", "Error: The Vary header must be a comma-separated list " "of header names, or \"*\"." }, { "contentrange", "Warning: The Content-Range header should not be returned " "by the server for this request." }, { "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 " "the future." }, { "missingcolon", "Error: Headers must be of the form 'Name: value'." }, { "missingcontenttype", "Warning: No Content-Type header was present. The " "client will have to guess the media type or ask " "the user. Adding a Content-Type header is strongly " "recommended." }, { "missingcontlang", "Consider adding a Content-Language header if " "applicable for this document." }, { "missingdate", "Warning: No Date header was present. A Date header must " "be present, unless the server does not have a clock, or " "the response is 100, 101, or 500 - 599." }, { "missinglastmod", "No Last-Modified header was present. The " "HTTP/1.1 specification states that this header should " "be sent whenever feasible." }, { "nocharset", "Warning: No character set is specified in the Content-Type. " "Clients may assume the default of ISO-8859-1. Consider " "appending '; charset=...'." }, { "nonstandard", "Warning: I don't know anything about this header. Is it " "a standard HTTP response header?" }, { "notcrlf", "Error: This header line does not end in CR LF. HTTP requires " "that all header lines end with CR LF." }, { "ok", "OK." }, { "oldhttp", "Warning: This version of HTTP is obsolete. Consider upgrading " "to HTTP/1.1." }, { "rfc1036", "Warning: This date is in the obsolete RFC 1036 format. " "Consider using the RFC 1123 format instead." }, { "ugly", "This URL appears to contain implementation-specific parts such " "as an extension or a query string. This may make the URL liable " "to change when the implementation is changed, resulting in " "broken links. Consider using URL rewriting or equivalent to " "implement a future-proof URL space. See " "http://www.w3.org/Provider/Style/URI for more information." }, { "unknowncachecont", "Warning: This Cache-Control directive is " "non-standard and will have limited support." }, { "unknowncontenc", "Warning: This is not a standard Content-Encoding." }, { "unknownrange", "Warning: This range unit is not a standard HTTP/1.1 " "range." }, { "unknowntransenc", "Warning: This is not a standard Transfer-Encoding." }, { "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." } }; /** * Look up and output the string referenced by a key. */ void lookup(const char *key) { const char *s, *spc; int x; struct message_entry *message; message = bsearch(key, message_table, sizeof message_table / sizeof message_table[0], sizeof message_table[0], (int (*)(const void *, const void *)) strcasecmp); if (message) s = message->value; else s = key; printf(" "); x = 4; while (*s) { spc = strchr(s, ' '); if (!spc) spc = s + strlen(s); if (75 < x + (spc - s)) { printf("\n "); x = 4; } x += spc - s + 1; printf("%.*s ", spc - s, s); if (*spc) s = spc + 1; else s = spc; } printf("\n\n"); }