/[james]/httplint/httplint.c
ViewVC logotype

Annotation of /httplint/httplint.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 40 - (hide annotations) (download) (as text)
Mon Dec 15 15:07:31 2003 UTC (20 years, 4 months ago) by james
File MIME type: text/x-csrc
File size: 26973 byte(s)
Initial import.

1 james 40 /*
2     * HTTP Header Lint
3     * Licensed under the same license as Curl
4     * http://curl.haxx.se/docs/copyright.html
5     * Copyright 2003 James Bursa <bursa@users.sourceforge.net>
6     */
7    
8     /*
9     * Compile using
10     * gcc -W -Wall `curl-config --cflags --libs` -o httplint httplint.c
11     *
12     * References of the form [6.1.1] are to RFC 2616 (HTTP/1.1).
13     */
14    
15     #define _GNU_SOURCE
16     #define __USE_XOPEN
17    
18     #include <limits.h>
19     #include <math.h>
20     #include <stdbool.h>
21     #include <stdio.h>
22     #include <stdlib.h>
23     #include <string.h>
24     #include <time.h>
25     #include <sys/types.h>
26     #include <regex.h>
27     #include <curl/curl.h>
28    
29    
30     #define NUMBER "0123456789"
31     #define UNUSED(x) x = x
32    
33    
34     bool start;
35     CURL *curl;
36     int status_code;
37     char error_buffer[CURL_ERROR_SIZE];
38     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;
40    
41    
42     void init(void);
43     void regcomp_wrapper(regex_t *preg, const char *regex, int cflags);
44     void check_url(const char *url);
45     size_t header_callback(char *ptr, size_t msize, size_t nmemb, void *stream);
46     size_t data_callback(void *ptr, size_t size, size_t nmemb, void *stream);
47     void check_status_line(const char *s);
48     void check_header(const char *name, const char *value);
49     bool parse_date(const char *s, struct tm *tm);
50     const char *skip_lws(const char *s);
51     bool parse_list(const char *s, regex_t *preg, unsigned int n, unsigned int m,
52     void (*callback)(const char *s, regmatch_t pmatch[]));
53     void header_accept_ranges(const char *s);
54     void header_age(const char *s);
55     void header_allow(const char *s);
56     void header_cache_control(const char *s);
57     void header_cache_control_callback(const char *s, regmatch_t pmatch[]);
58     void header_connection(const char *s);
59     void header_content_encoding(const char *s);
60     void header_content_encoding_callback(const char *s, regmatch_t pmatch[]);
61     void header_content_language(const char *s);
62     void header_content_length(const char *s);
63     void header_content_location(const char *s);
64     void header_content_md5(const char *s);
65     void header_content_range(const char *s);
66     void header_content_type(const char *s);
67     void header_date(const char *s);
68     void header_etag(const char *s);
69     void header_expires(const char *s);
70     void header_last_modified(const char *s);
71     void header_location(const char *s);
72     void header_pragma(const char *s);
73     void header_retry_after(const char *s);
74     void header_server(const char *s);
75     void header_trailer(const char *s);
76     void header_transfer_encoding(const char *s);
77     void header_transfer_encoding_callback(const char *s, regmatch_t pmatch[]);
78     void header_upgrade(const char *s);
79     void header_vary(const char *s);
80     void header_via(const char *s);
81     void die(const char *error);
82     void warning(const char *message);
83     void error(const char *message);
84     void print(const char *s, size_t len);
85     void lookup(const char *key);
86    
87    
88     struct header_entry {
89     char name[40];
90     void (*handler)(const char *s);
91     int count;
92     char *missing;
93     } header_table[] = {
94     { "Accept-Ranges", header_accept_ranges, 0, 0 },
95     { "Age", header_age, 0, 0 },
96     { "Allow", header_allow, 0, 0 },
97     { "Cache-Control", header_cache_control, 0, 0 },
98     { "Connection", header_connection, 0, 0 },
99     { "Content-Encoding", header_content_encoding, 0, 0 },
100     { "Content-Language", header_content_language, 0, "missingcontlang" },
101     { "Content-Length", header_content_length, 0, 0 },
102     { "Content-Location", header_content_location, 0, 0 },
103     { "Content-MD5", header_content_md5, 0, 0 },
104     { "Content-Range", header_content_range, 0, 0 },
105     { "Content-Type", header_content_type, 0, "missingcontenttype" },
106     { "Date", header_date, 0, "missingdate" },
107     { "ETag", header_etag, 0, 0 },
108     { "Expires", header_expires, 0, 0 },
109     { "Last-Modified", header_last_modified, 0, "missinglastmod" },
110     { "Location", header_location, 0, 0 },
111     { "Pragma", header_pragma, 0, 0 },
112     { "Retry-After", header_retry_after, 0, 0 },
113     { "Server", header_server, 0, 0 },
114     { "Trailer", header_trailer, 0, 0 },
115     { "Transfer-Encoding", header_transfer_encoding, 0, 0 },
116     { "Upgrade", header_upgrade, 0, 0 },
117     { "Vary", header_vary, 0, 0 },
118     { "Via", header_via, 0, 0 }
119     };
120    
121    
122     /**
123     * Main entry point.
124     */
125     int main(int argc, char *argv[])
126     {
127     int i;
128    
129     if (argc < 2)
130     die("Usage: httplint url [url ...]");
131    
132     init();
133    
134     for (i = 1; i != argc; i++)
135     check_url(argv[i]);
136    
137     curl_global_cleanup();
138    
139     return 0;
140     }
141    
142    
143     /**
144     * Initialise the curl handle and compile regular expressions.
145     */
146     void init(void)
147     {
148     struct curl_slist *request_headers = 0;
149    
150     if (curl_global_init(CURL_GLOBAL_ALL))
151     die("Failed to initialise libcurl");
152    
153     curl = curl_easy_init();
154     if (!curl)
155     die("Failed to create curl handle");
156    
157     if (curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback))
158     die("Failed to set curl options");
159     if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, data_callback))
160     die("Failed to set curl options");
161     if (curl_easy_setopt(curl, CURLOPT_USERAGENT, "httplint"))
162     die("Failed to set curl options");
163     if (curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer))
164     die("Failed to set curl options");
165    
166     /* remove libcurl default headers */
167     request_headers = curl_slist_append(request_headers, "Accept:");
168     request_headers = curl_slist_append(request_headers, "Pragma:");
169     if (curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers))
170     die("Failed to set curl options");
171    
172     /* compile regular expressions */
173     regcomp_wrapper(&re_status_line,
174     "^HTTP/([0-9]+)[.]([0-9]+) ([0-9][0-9][0-9]) ([\t -~�-�]*)$",
175     REG_EXTENDED);
176     regcomp_wrapper(&re_token,
177     "^([-0-9a-zA-Z_.]+)",
178     REG_EXTENDED);
179     regcomp_wrapper(&re_token_value,
180     "^([-0-9a-zA-Z_.]+)(=([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\"))?",
181     REG_EXTENDED);
182     regcomp_wrapper(&re_content_type,
183     "^([-0-9a-zA-Z_.]+)/([-0-9a-zA-Z_.]+)[ \t]*"
184     "(;[ \t]*([-0-9a-zA-Z_.]+)="
185     "([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\")[ \t]*)*$",
186     REG_EXTENDED);
187     regcomp_wrapper(&re_absolute_uri,
188     "^[a-zA-Z0-9]+://[^ ]+$",
189     REG_EXTENDED);
190     regcomp_wrapper(&re_etag,
191     "^(W/[ \t]*)?\"([^\"]|[\\].)*\"$",
192     REG_EXTENDED);
193     regcomp_wrapper(&re_server,
194     "^((([-0-9a-zA-Z_.]+(/[-0-9a-zA-Z_.]+)?)|(\\(.*\\)))[ \t]*)+$",
195     REG_EXTENDED);
196     regcomp_wrapper(&re_transfer_coding,
197     "^([-0-9a-zA-Z_.]+)[ \t]*"
198     "(;[ \t]*([-0-9a-zA-Z_.]+)="
199     "([-0-9a-zA-Z_.]+|\"([^\"]|[\\].)*\")[ \t]*)*$",
200     REG_EXTENDED);
201     regcomp_wrapper(&re_upgrade,
202     "^([-0-9a-zA-Z_.](/[-0-9a-zA-Z_.])?)+$",
203     REG_EXTENDED);
204     regcomp_wrapper(&re_ugly,
205     "^[a-zA-Z0-9]+://[^/]+[/a-zA-Z0-9-_]*$",
206     REG_EXTENDED);
207     }
208    
209    
210     /**
211     * Compile a regular expression, handling errors.
212     */
213     void regcomp_wrapper(regex_t *preg, const char *regex, int cflags)
214     {
215     char errbuf[200];
216     int r;
217     r = regcomp(preg, regex, cflags);
218     if (r) {
219     regerror(r, preg, errbuf, sizeof errbuf);
220     fprintf(stderr, "Failed to compile regexp '%s'\n", regex);
221     die(errbuf);
222     }
223     }
224    
225    
226     /**
227     * Fetch and check the headers for the specified url.
228     */
229     void check_url(const char *url)
230     {
231     int i, r;
232     CURLcode code;
233    
234     start = true;
235     for (i = 0; i != sizeof header_table / sizeof header_table[0]; i++)
236     header_table[i].count = 0;
237    
238     printf("Checking URL %s\n", url);
239     if (strncmp(url, "http", 4))
240     warning("this is not an http or https url");
241    
242     if (curl_easy_setopt(curl, CURLOPT_URL, url))
243     die("Failed to set curl options");
244    
245     code = curl_easy_perform(curl);
246     if (code != CURLE_OK && code != CURLE_WRITE_ERROR) {
247     error(error_buffer);
248     return;
249     } else {
250     printf("\n");
251     for (i = 0; i != sizeof header_table / sizeof header_table[0]; i++) {
252     if (header_table[i].count == 0 && header_table[i].missing)
253     lookup(header_table[i].missing);
254     }
255     }
256    
257     r = regexec(&re_ugly, url, 0, 0, 0);
258     if (r)
259     lookup("ugly");
260     }
261    
262    
263     /**
264     * Callback for received header data.
265     */
266     size_t header_callback(char *ptr, size_t msize, size_t nmemb, void *stream)
267     {
268     const size_t size = msize * nmemb;
269     char s[400], *name, *value;
270    
271     UNUSED(stream);
272    
273     printf("* ");
274     print(ptr, size);
275     printf("\n");
276    
277     if (size < 2 || ptr[size - 2] != 13 || ptr[size - 1] != 10) {
278     lookup("notcrlf");
279     return size;
280     }
281     if (sizeof s <= size) {
282     warning("header too long: ignored\n");
283     return size;
284     }
285     strncpy(s, ptr, size);
286     s[size - 2] = 0;
287    
288     name = s;
289     value = strchr(s, ':');
290    
291     if (s[0] == 0) {
292     /* empty header indicates end of headers */
293     puts("End of headers.");
294     return 0;
295    
296     } else if (start) {
297     /* Status-Line [6.1] */
298     check_status_line(s);
299     start = false;
300    
301     } else if (!value) {
302     lookup("missingcolon");
303    
304     } else {
305     *value = 0;
306     value++;
307    
308     check_header(name, skip_lws(value));
309     }
310    
311     return size;
312     }
313    
314    
315     /**
316     * Callback for received body data.
317     *
318     * We are not interested in the body, so abort the fetch by returning 0.
319     */
320     size_t data_callback(void *ptr, size_t size, size_t nmemb, void *stream)
321     {
322     UNUSED(ptr);
323     UNUSED(size);
324     UNUSED(nmemb);
325     UNUSED(stream);
326    
327     return 0;
328     }
329    
330    
331     /**
332     * Check the syntax and content of the response Status-Line [6.1].
333     */
334     void check_status_line(const char *s)
335     {
336     const char *reason;
337     unsigned int major = 0, minor = 0;
338     int r;
339     regmatch_t pmatch[5];
340    
341     r = regexec(&re_status_line, s, 5, pmatch, 0);
342     if (r) {
343     lookup("badstatusline");
344     return;
345     }
346    
347     major = atoi(s + pmatch[1].rm_so);
348     minor = atoi(s + pmatch[2].rm_so);
349     status_code = atoi(s + pmatch[3].rm_so);
350     reason = s + pmatch[4].rm_so;
351    
352     if (major < 1 || (major == 1 && minor == 0)) {
353     lookup("oldhttp");
354     } else if ((major == 1 && 1 < minor) || 1 < major) {
355     lookup("futurehttp");
356     } else {
357     if (status_code < 100 || 600 <= status_code) {
358     lookup("badstatus");
359     } else {
360     char key[] = "xxx";
361     key[0] = '0' + status_code / 100;
362     lookup(key);
363     }
364     }
365     }
366    
367    
368     /**
369     * Check the syntax and content of a header.
370     */
371     void check_header(const char *name, const char *value)
372     {
373     struct header_entry *header;
374    
375     header = bsearch(name, header_table,
376     sizeof header_table / sizeof header_table[0],
377     sizeof header_table[0],
378     (int (*)(const void *, const void *)) strcasecmp);
379    
380     if (header) {
381     header->count++;
382     header->handler(value);
383     } else
384     lookup("nonstandard");
385     }
386    
387    
388     /**
389     * Attempt to parse an HTTP Full Date (3.3.1), returning true on success.
390     */
391     bool parse_date(const char *s, struct tm *tm)
392     {
393     char *r;
394     int len = strlen(s);
395    
396     if (len == 29) {
397     /* RFC 1123 */
398     r = strptime(s, "%a, %d %b %Y %H:%M:%S GMT", tm);
399     if (r == s + len)
400     return true;
401    
402     } else if (len == 24) {
403     /* asctime() format */
404     r = strptime(s, "%a %b %d %H:%M:%S %Y", tm);
405     if (r == s + len) {
406     lookup("asctime");
407     return true;
408     }
409     r = strptime(s, "%a %b %d %H:%M:%S %Y", tm);
410     if (r == s + len) {
411     lookup("asctime");
412     return true;
413     }
414    
415     } else {
416     /* RFC 1036 */
417     r = strptime(s, "%a, %d-%b-%y %H:%M:%S GMT", tm);
418     if (r == s + len) {
419     lookup("rfc1036");
420     return true;
421     }
422    
423     }
424    
425     lookup("baddate");
426     return false;
427     }
428    
429    
430     /**
431     * Skip optional LWS (linear white space) [2.2]
432     */
433     const char *skip_lws(const char *s)
434     {
435     if (s[0] == 13 && s[1] == 10 && (s[2] == ' ' || s[2] == '\t'))
436     s += 2;
437     while (*s == ' ' || *s == '\t')
438     s++;
439     return s;
440     }
441    
442    
443     /**
444     * Parse a list of elements (#rule in [2.1]).
445     */
446     bool parse_list(const char *s, regex_t *preg, unsigned int n, unsigned int m,
447     void (*callback)(const char *s, regmatch_t pmatch[]))
448     {
449     int r;
450     unsigned int items = 0;
451     regmatch_t pmatch[20];
452    
453     do {
454     r = regexec(preg, s, 20, pmatch, 0);
455     if (r) {
456     printf(" Failed to match list item %i\n", items + 1);
457     return false;
458     }
459    
460     if (callback)
461     callback(s, pmatch);
462     items++;
463    
464     s += pmatch[0].rm_eo;
465     s = skip_lws(s);
466     if (*s == 0)
467     break;
468     if (*s != ',') {
469     printf(" Expecting , after list item %i\n", items);
470     return false;
471     }
472     while (*s == ',')
473     s = skip_lws(s + 1);
474     } while (*s != 0);
475    
476     if (items < n || m < items) {
477     printf(" %i items in list, but there should be ", items);
478     if (m == UINT_MAX)
479     printf("at least %i\n", n);
480     else
481     printf("between %i and %i\n", n, m);
482     return false;
483     }
484    
485     return true;
486     }
487    
488    
489     /* Header-specific validation. */
490     void header_accept_ranges(const char *s)
491     {
492     if (strcmp(s, "bytes") == 0)
493     lookup("ok");
494     else if (strcmp(s, "none") == 0)
495     lookup("ok");
496     else
497     lookup("unknownrange");
498     }
499    
500     void header_age(const char *s)
501     {
502     if (s[0] == 0 || strspn(s, NUMBER) != strlen(s))
503     lookup("badage");
504     else
505     lookup("ok");
506     }
507    
508     void header_allow(const char *s)
509     {
510     if (parse_list(s, &re_token, 0, UINT_MAX, 0))
511     lookup("ok");
512     else
513     lookup("badallow");
514     }
515    
516     void header_cache_control(const char *s)
517     {
518     if (parse_list(s, &re_token_value, 1, UINT_MAX,
519     header_cache_control_callback))
520     lookup("ok");
521     else
522     lookup("badcachecont");
523     }
524    
525     char cache_control_list[][20] = {
526     "max-age", "max-stale", "min-fresh", "must-revalidate",
527     "no-cache", "no-store", "no-transform", "only-if-cached",
528     "private", "proxy-revalidate", "public", "s-maxage"
529     };
530    
531     void header_cache_control_callback(const char *s, regmatch_t pmatch[])
532     {
533     size_t len = pmatch[1].rm_eo - pmatch[1].rm_so;
534     char name[20];
535     char *dir;
536    
537     if (19 < len) {
538     lookup("unknowncachecont");
539     return;
540     }
541    
542     strncpy(name, s + pmatch[1].rm_so, len);
543     name[len] = 0;
544    
545     dir = bsearch(name, cache_control_list,
546     sizeof cache_control_list / sizeof cache_control_list[0],
547     sizeof cache_control_list[0],
548     (int (*)(const void *, const void *)) strcasecmp);
549    
550     if (!dir) {
551     printf(" Cache-Control directive '%s':\n", name);
552     lookup("unknowncachecont");
553     }
554     }
555    
556     void header_connection(const char *s)
557     {
558     if (strcmp(s, "close") == 0)
559     lookup("ok");
560     else
561     lookup("badconnection");
562     }
563    
564     void header_content_encoding(const char *s)
565     {
566     if (parse_list(s, &re_token, 1, UINT_MAX,
567     header_content_encoding_callback))
568     lookup("ok");
569     else
570     lookup("badcontenc");
571     }
572    
573     char content_coding_list[][20] = {
574     "compress", "deflate", "gzip", "identity"
575     };
576    
577     void header_content_encoding_callback(const char *s, regmatch_t pmatch[])
578     {
579     size_t len = pmatch[1].rm_eo - pmatch[1].rm_so;
580     char name[20];
581     char *dir;
582    
583     if (19 < len) {
584     lookup("unknowncontenc");
585     return;
586     }
587    
588     strncpy(name, s + pmatch[1].rm_so, len);
589     name[len] = 0;
590    
591     dir = bsearch(name, content_coding_list,
592     sizeof content_coding_list / sizeof content_coding_list[0],
593     sizeof content_coding_list[0],
594     (int (*)(const void *, const void *)) strcasecmp);
595     if (!dir) {
596     printf(" Content-Encoding '%s':\n", name);
597     lookup("unknowncontenc");
598     }
599     }
600    
601     void header_content_language(const char *s)
602     {
603     if (parse_list(s, &re_token, 1, UINT_MAX, 0))
604     lookup("ok");
605     else
606     lookup("badcontlang");
607     }
608    
609     void header_content_length(const char *s)
610     {
611     if (s[0] == 0 || strspn(s, NUMBER) != strlen(s))
612     lookup("badcontlen");
613     else
614     lookup("ok");
615     }
616    
617     void header_content_location(const char *s)
618     {
619     if (strchr(s, ' '))
620     lookup("badcontloc");
621     else
622     lookup("ok");
623     }
624    
625     void header_content_md5(const char *s)
626     {
627     if (strlen(s) != 24)
628     lookup("badcontmd5");
629     else
630     lookup("ok");
631     }
632    
633     void header_content_range(const char *s)
634     {
635     UNUSED(s);
636     lookup("contentrange");
637     }
638    
639     void header_content_type(const char *s)
640     {
641     bool charset = false;
642     char *type, *subtype;
643     unsigned int i;
644     int r;
645     regmatch_t pmatch[30];
646    
647     r = regexec(&re_content_type, s, 30, pmatch, 0);
648     if (r) {
649     lookup("badcontenttype");
650     return;
651     }
652    
653     type = strndup(s + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so);
654     subtype = strndup(s + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so);
655    
656     /* parameters */
657     for (i = 3; i != 30 && pmatch[i].rm_so != -1; i += 3) {
658     char *attrib, *value;
659    
660     attrib = strndup(s + pmatch[i + 1].rm_so,
661     pmatch[i + 1].rm_eo - pmatch[i + 1].rm_so);
662     value = strndup(s + pmatch[i + 2].rm_so,
663     pmatch[i + 2].rm_eo - pmatch[i + 2].rm_so);
664    
665     if (strcasecmp(attrib, "charset") == 0)
666     charset = true;
667     }
668    
669     if (strcasecmp(type, "text") == 0 && !charset)
670     lookup("nocharset");
671     else
672     lookup("ok");
673     }
674    
675     void header_date(const char *s)
676     {
677     double diff;
678     time_t time0, time1;
679     struct tm tm;
680    
681     time0 = time(0);
682     if (!parse_date(s, &tm))
683     return;
684     time1 = mktime(&tm);
685    
686     diff = difftime(time0, time1);
687     if (10 < fabs(diff))
688     lookup("wrongdate");
689     else
690     lookup("ok");
691     }
692    
693     void header_etag(const char *s)
694     {
695     int r;
696     r = regexec(&re_etag, s, 0, 0, 0);
697     if (r)
698     lookup("badetag");
699     else
700     lookup("ok");
701     }
702    
703     void header_expires(const char *s)
704     {
705     struct tm tm;
706     if (parse_date(s, &tm))
707     lookup("ok");
708     }
709    
710     void header_last_modified(const char *s)
711     {
712     double diff;
713     time_t time0, time1;
714     struct tm tm;
715    
716     time0 = time(0);
717     if (!parse_date(s, &tm))
718     return;
719     time1 = mktime(&tm);
720    
721     diff = difftime(time1, time0);
722     if (10 < diff)
723     lookup("futurelastmod");
724     else
725     lookup("ok");
726     }
727    
728     void header_location(const char *s)
729     {
730     int r;
731     r = regexec(&re_absolute_uri, s, 0, 0, 0);
732     if (r)
733     lookup("badlocation");
734     else
735     lookup("ok");
736     }
737    
738     void header_pragma(const char *s)
739     {
740     if (parse_list(s, &re_token_value, 1, UINT_MAX, 0))
741     lookup("ok");
742     else
743     lookup("badpragma");
744     }
745    
746     void header_retry_after(const char *s)
747     {
748     struct tm tm;
749    
750     if (s[0] != 0 && strspn(s, NUMBER) == strlen(s)) {
751     lookup("ok");
752     return;
753     }
754    
755     if (!parse_date(s, &tm))
756     return;
757    
758     lookup("ok");
759     }
760    
761     void header_server(const char *s)
762     {
763     int r;
764     r = regexec(&re_server, s, 0, 0, 0);
765     if (r)
766     lookup("badserver");
767     else
768     lookup("ok");
769     }
770    
771     void header_trailer(const char *s)
772     {
773     if (parse_list(s, &re_token, 1, UINT_MAX, 0))
774     lookup("ok");
775     else
776     lookup("badtrailer");
777     }
778    
779     void header_transfer_encoding(const char *s)
780     {
781     if (parse_list(s, &re_transfer_coding, 1, UINT_MAX,
782     header_transfer_encoding_callback))
783     lookup("ok");
784     else
785     lookup("badtransenc");
786     }
787    
788     char transfer_coding_list[][20] = {
789     "chunked", "compress", "deflate", "gzip", "identity"
790     };
791    
792     void header_transfer_encoding_callback(const char *s, regmatch_t pmatch[])
793     {
794     size_t len = pmatch[1].rm_eo - pmatch[1].rm_so;
795     char name[20];
796     char *dir;
797    
798     if (19 < len) {
799     lookup("unknowntransenc");
800     return;
801     }
802    
803     strncpy(name, s + pmatch[1].rm_so, len);
804     name[len] = 0;
805    
806     dir = bsearch(name, transfer_coding_list,
807     sizeof transfer_coding_list / sizeof transfer_coding_list[0],
808     sizeof transfer_coding_list[0],
809     (int (*)(const void *, const void *)) strcasecmp);
810     if (!dir) {
811     printf(" Transfer-Encoding '%s':\n", name);
812     lookup("unknowntransenc");
813     }
814     }
815    
816     void header_upgrade(const char *s)
817     {
818     int r;
819     r = regexec(&re_upgrade, s, 0, 0, 0);
820     if (r)
821     lookup("badupgrade");
822     else
823     lookup("ok");
824     }
825    
826     void header_vary(const char *s)
827     {
828     if (strcmp(s, "*") == 0 || parse_list(s, &re_token, 1, UINT_MAX, 0))
829     lookup("ok");
830     else
831     lookup("badvary");
832     }
833    
834     void header_via(const char *s)
835     {
836     UNUSED(s);
837     lookup("via");
838     }
839    
840    
841     /**
842     * Print an error message and exit.
843     */
844     void die(const char *error)
845     {
846     fprintf(stderr, "httplint: %s\n", error);
847     exit(EXIT_FAILURE);
848     }
849    
850    
851     /**
852     * Print a warning message.
853     */
854     void warning(const char *message)
855     {
856     printf("Warning: %s\n", message);
857     }
858    
859    
860     /**
861     * Print an error message.
862     */
863     void error(const char *message)
864     {
865     printf("Error: %s\n", message);
866     }
867    
868    
869     /**
870     * Print a string which contains control characters.
871     */
872     void print(const char *s, size_t len)
873     {
874     size_t i;
875     for (i = 0; i != len; i++) {
876     if (31 < s[i] && s[i] < 127)
877     putchar(s[i]);
878     else
879     printf("[%.2x]", s[i]);
880     }
881     }
882    
883    
884     struct message_entry {
885     const char key[20];
886     const char *value;
887     } message_table[] = {
888     { "1xx", "A response status code in the range 100 - 199 indicates a "
889     "'provisional response'." },
890     { "2xx", "A response status code in the range 200 - 299 indicates that "
891     "the request was successful." },
892     { "3xx", "A response status code in the range 300 - 399 indicates that "
893     "the client should redirect to a new URL." },
894     { "4xx", "A response status code in the range 400 - 499 indicates that "
895     "the request could not be fulfilled due to client error." },
896     { "5xx", "A response status code in the range 500 - 599 indicates that "
897     "an error occurred on the server." },
898     { "asctime", "Warning: This date is in the obsolete asctime() format. "
899     "Consider using the RFC 1123 format instead." },
900     { "badage", "Error: The Age header must be one number." },
901     { "badallow", "Error: The Allow header must be a comma-separated list of "
902     "HTTP methods." },
903     { "badcachecont", "Error: The Cache-Control header must be a "
904     "comma-separated list of directives." },
905     { "badconnection", "Warning: The only value of the Connection header "
906     "defined by HTTP/1.1 is \"close\"." },
907     { "badcontenc", "Error: The Content-Encoding header must be a "
908     "comma-separated list of encodings." },
909     { "badcontenttype", "Error: The Content-Type header must be of the form "
910     "'type/subtype (; optional parameters)'." },
911     { "badcontlang", "Error: The Content-Language header must be a "
912     "comma-separated list of language tags." },
913     { "badcontlen", "Error: The Content-Length header must be a number." },
914     { "badcontloc", "Error: The Content-Location header must be an absolute "
915     "or relative URI." },
916     { "badcontmd5", "Error: The Content-MD5 header must be a base64 encoded "
917     "MD5 sum." },
918     { "baddate", "Error: Failed to parse this date. Dates should be in the RFC "
919     "1123 format." },
920     { "badetag", "Error: The ETag header must be a quoted string (optionally "
921     "preceded by \"W/\" for a weak tag)." },
922     { "badlocation", "Error: The Location header must be an absolute URI. "
923     "Relative URIs are not permitted." },
924     { "badpragma", "Error: The Pragma header must be a comma-separated list of "
925     "directives." },
926     { "badserver", "Error: The Server header must be a space-separated list of "
927     "products of the form Name/optional-version and comments "
928     "in ()." },
929     { "badstatus", "Warning: The response status code is outside the standard "
930     "range 100 - 599." },
931     { "badstatusline", "Error: Failed to parse the response Status-Line. The "
932     "status line must be of the form 'HTTP/n.n <3-digit "
933     "status> <reason phrase>'." },
934     { "badtrailer", "Error: The Trailer header must be a comma-separated list "
935     "of header names." },
936     { "badtransenc", "Error: The Transfer-Encoding header must be a "
937     "comma-separated of encodings." },
938     { "badupgrade", "Error: The Upgrade header must be a comma-separated list "
939     "of product identifiers." },
940     { "badvary", "Error: The Vary header must be a comma-separated list "
941     "of header names, or \"*\"." },
942     { "contentrange", "Warning: The Content-Range header should not be returned "
943     "by the server for this request." },
944     { "futurehttp", "Warning: I only understand HTTP/1.1. Check for a newer "
945     "version of this tool." },
946     { "futurelastmod", "Error: The specified Last-Modified date-time is in "
947     "the future." },
948     { "missingcolon", "Error: Headers must be of the form 'Name: value'." },
949     { "missingcontenttype", "Warning: No Content-Type header was present. The "
950     "client will have to guess the media type or ask "
951     "the user. Adding a Content-Type header is strongly "
952     "recommended." },
953     { "missingcontlang", "Consider adding a Content-Language header if "
954     "applicable for this document." },
955     { "missingdate", "Warning: No Date header was present. A Date header must "
956     "be present, unless the server does not have a clock, or "
957     "the response is 100, 101, or 500 - 599." },
958     { "missinglastmod", "No Last-Modified header was present. The "
959     "HTTP/1.1 specification states that this header should "
960     "be sent whenever feasible." },
961     { "nocharset", "Warning: No character set is specified in the Content-Type. "
962     "Clients may assume the default of ISO-8859-1. Consider "
963     "appending '; charset=...'." },
964     { "nonstandard", "Warning: I don't know anything about this header. Is it "
965     "a standard HTTP response header?" },
966     { "notcrlf", "Error: This header line does not end in CR LF. HTTP requires "
967     "that all header lines end with CR LF." },
968     { "ok", "OK." },
969     { "oldhttp", "Warning: This version of HTTP is obsolete. Consider upgrading "
970     "to HTTP/1.1." },
971     { "rfc1036", "Warning: This date is in the obsolete RFC 1036 format. "
972     "Consider using the RFC 1123 format instead." },
973     { "ugly", "This URL appears to contain implementation-specific parts such "
974     "as an extension or a query string. This may make the URL liable "
975     "to change when the implementation is changed, resulting in "
976     "broken links. Consider using URL rewriting or equivalent to "
977     "implement a future-proof URL space. See "
978     "http://www.w3.org/Provider/Style/URI for more information." },
979     { "unknowncachecont", "Warning: This Cache-Control directive is "
980     "non-standard and will have limited support." },
981     { "unknowncontenc", "Warning: This is not a standard Content-Encoding." },
982     { "unknownrange", "Warning: This range unit is not a standard HTTP/1.1 "
983     "range." },
984     { "unknowntransenc", "Warning: This is not a standard Transfer-Encoding." },
985     { "via", "This header was added by a proxy, cache or gateway." },
986     { "wrongdate", "Warning: The server date-time differs from this system's "
987     "date-time by more than 10 seconds. Check that both the "
988     "system clocks are correct." }
989     };
990    
991    
992     /**
993     * Look up and output the string referenced by a key.
994     */
995     void lookup(const char *key)
996     {
997     const char *s, *spc;
998     int x;
999     struct message_entry *message;
1000    
1001     message = bsearch(key, message_table,
1002     sizeof message_table / sizeof message_table[0],
1003     sizeof message_table[0],
1004     (int (*)(const void *, const void *)) strcasecmp);
1005     if (message)
1006     s = message->value;
1007     else
1008     s = key;
1009    
1010     printf(" ");
1011     x = 4;
1012     while (*s) {
1013     spc = strchr(s, ' ');
1014     if (!spc)
1015     spc = s + strlen(s);
1016     if (75 < x + (spc - s)) {
1017     printf("\n ");
1018     x = 4;
1019     }
1020     x += spc - s + 1;
1021     printf("%.*s ", spc - s, s);
1022     if (*spc)
1023     s = spc + 1;
1024     else
1025     s = spc;
1026     }
1027     printf("\n\n");
1028     }

  ViewVC Help
Powered by ViewVC 1.1.26