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

Diff of /httplint/httplint.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 40 by james, Mon Dec 15 15:07:31 2003 UTC revision 50 by james, Fri Feb 20 20:49:40 2004 UTC
# Line 1  Line 1 
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  /*  /*
# Line 36  CURL *curl; Line 36  CURL *curl;
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);
# Line 47  size_t data_callback(void *ptr, size_t s Line 48  size_t data_callback(void *ptr, size_t s
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[]));
# Line 78  void header_transfer_encoding_callback(c Line 81  void header_transfer_encoding_callback(c
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);
# Line 111  struct header_entry { Line 115  struct header_entry {
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 },
# Line 174  void init(void) Line 179  void init(void)
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]*"
# Line 191  void init(void) Line 196  void init(void)
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]*"
# Line 202  void init(void) Line 207  void init(void)
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    
# Line 380  void check_header(const char *name, cons Line 408  void check_header(const char *name, cons
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    
# Line 390  void check_header(const char *name, cons Line 421  void check_header(const char *name, cons
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      }      }
# Line 428  bool parse_date(const char *s, struct tm Line 477  bool parse_date(const char *s, struct tm
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)
# Line 681  void header_date(const char *s) Line 795  void header_date(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))
# Line 716  void header_last_modified(const char *s) Line 830  void header_last_modified(const char *s)
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)
# Line 837  void header_via(const char *s) Line 951  void header_via(const char *s)
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.
# Line 941  struct message_entry { Line 1136  struct message_entry {
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 "
# Line 985  struct message_entry { Line 1190  struct message_entry {
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    

Legend:
Removed from v.40  
changed lines
  Added in v.50

  ViewVC Help
Powered by ViewVC 1.1.26