[monitoring-plugins] added -M<m> age option for document age, using ...

Andreas Baumann git at monitoring-plugins.org
Fri Apr 14 21:20:12 CEST 2017


 Module: monitoring-plugins
 Branch: feature_check_curl
 Commit: f827df482be8cec9d6c9dee023ebf9191b890b9c
 Author: Andreas Baumann <mail at andreasbaumann.cc>
   Date: Fri Apr 14 21:10:40 2017 +0200
    URL: https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=f827df4

added -M<m> age option for document age, using picohttpparser from h2o (maybe handy
later to make a more robust header condition checker?)

---

 .gitignore               |   1 +
 plugins/Makefile.am      |   6 +-
 plugins/check_curl.c     | 203 +++++++++++++++-
 plugins/picohttpparser.c | 620 +++++++++++++++++++++++++++++++++++++++++++++++
 plugins/picohttpparser.h |  89 +++++++
 5 files changed, 912 insertions(+), 7 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7140318..5fa4c81 100644
--- a/.gitignore
+++ b/.gitignore
@@ -202,6 +202,7 @@ NP-VERSION-FILE
 /plugins/negate
 /plugins/stamp-h*
 /plugins/urlize
+/plugins/libpicohttpparser.a
 
 # /plugins/t/
 /plugins/t/*.tmp
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index ffd8baf..ff745c3 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -44,11 +44,13 @@ EXTRA_DIST = t tests
 
 PLUGINHDRS = common.h
 
-noinst_LIBRARIES = libnpcommon.a
+noinst_LIBRARIES = libnpcommon.a libpicohttpparser.a
 
 libnpcommon_a_SOURCES = utils.c netutils.c sslutils.c runcmd.c	\
 	popen.c utils.h netutils.h popen.h common.h runcmd.c runcmd.h
 
+libpicohttpparser_a_SOURCES = picohttpparser.c
+
 BASEOBJS = libnpcommon.a ../lib/libmonitoringplug.a ../gl/libgnu.a
 NETOBJS = $(BASEOBJS) $(EXTRA_NETOBLS)
 NETLIBS = $(NETOBJS) $(SOCKETLIBS)
@@ -71,7 +73,7 @@ check_apt_LDADD = $(BASEOBJS)
 check_cluster_LDADD = $(BASEOBJS)
 check_curl_CFLAGS = $(AM_CFLAGS) $(LIBCURLCFLAGS)
 check_curl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCURLINCLUDE)
-check_curl_LDADD = $(NETLIBS) $(LIBCURLLIBS) $(SSLOBJS)
+check_curl_LDADD = $(NETLIBS) $(LIBCURLLIBS) $(SSLOBJS) libpicohttpparser.a
 check_dbi_LDADD = $(NETLIBS) $(DBILIBS)
 check_dig_LDADD = $(NETLIBS)
 check_disk_LDADD = $(BASEOBJS)
diff --git a/plugins/check_curl.c b/plugins/check_curl.c
index 4129acc..7576b2d 100644
--- a/plugins/check_curl.c
+++ b/plugins/check_curl.c
@@ -37,6 +37,8 @@ const char *progname = "check_curl";
 const char *copyright = "2006-2017";
 const char *email = "devel at monitoring-plugins.org";
 
+#include <ctype.h>
+
 #include "common.h"
 #include "utils.h"
 
@@ -47,6 +49,8 @@ const char *email = "devel at monitoring-plugins.org";
 #include "curl/curl.h"
 #include "curl/easy.h"
 
+#include "picohttpparser.h"
+
 #define MAKE_LIBCURL_VERSION(major, minor, patch) ((major)*0x10000 + (minor)*0x100 + (patch))
 
 #define DEFAULT_BUFFER_SIZE 2048
@@ -73,7 +77,7 @@ typedef struct {
   int http_code;    /* HTTP return code as in RFC 2145 */
   int http_subcode; /* Microsoft IIS extension, HTTP subcodes, see
                      * http://support.microsoft.com/kb/318380/en-us */
-  char *msg;        /* the human readable message */
+  const char *msg;  /* the human readable message */
   char *first_line; /* a copy of the first line */
 } curlhelp_statusline;
 
@@ -142,6 +146,7 @@ char *client_privkey = NULL;
 char *ca_cert = NULL;
 X509 *cert = NULL;
 int no_body = FALSE;
+int maximum_age = -1;
 int address_family = AF_UNSPEC;
 
 int process_arguments (int, char**);
@@ -156,6 +161,9 @@ void curlhelp_freebuffer (curlhelp_curlbuf*);
 int curlhelp_parse_statusline (const char*, curlhelp_statusline *);
 void curlhelp_free_statusline (curlhelp_statusline *);
 char *perfd_time_ssl (double microsec);
+char *get_header_value (const struct phr_header* headers, const size_t nof_headers, const char* header);
+static time_t parse_time_string (const char *string);
+int check_document_dates (const curlhelp_curlbuf *, char (*msg)[DEFAULT_BUFFER_SIZE]);
 
 void remove_newlines (char *);
 void test_file (char *);
@@ -391,7 +399,7 @@ check_http (void)
   /* Curl errors, result in critical Nagios state */
   if (res != CURLE_OK) {
     remove_newlines (errbuf);
-    snprintf (msg, DEFAULT_BUFFER_SIZE, _("Invalid HTTP response received from host on port %d: cURL returned %d - %s\n"),
+    snprintf (msg, DEFAULT_BUFFER_SIZE, _("Invalid HTTP response received from host on port %d: cURL returned %d - %s"),
       server_port, res, curl_easy_strerror(res));
     die (STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg);
   }
@@ -506,6 +514,10 @@ check_http (void)
       status_line.http_code, status_line.msg, code);
   }
 
+  if (maximum_age >= 0) {
+    result = max_state_alt(check_document_dates(&header_buf, &msg), result);
+  }
+
   /* Page and Header content checks go here */
 
   if (strlen (header_expect)) {
@@ -638,6 +650,7 @@ process_arguments (int argc, char **argv)
     {"useragent", required_argument, 0, 'A'},
     {"header", required_argument, 0, 'k'},
     {"no-body", no_argument, 0, 'N'},
+    {"max-age", required_argument, 0, 'M'},
     {"content-type", required_argument, 0, 'T'},
     {"pagesize", required_argument, 0, 'm'},
     {"invert-regex", no_argument, NULL, INVERT_REGEX},
@@ -665,7 +678,7 @@ process_arguments (int argc, char **argv)
   }
 
   while (1) {
-    c = getopt_long (argc, argv, "Vvh46t:c:w:A:k:H:P:j:T:I:a:p:d:e:s:R:r:u:f:C:J:K:S::m:NE", longopts, &option);
+    c = getopt_long (argc, argv, "Vvh46t:c:w:A:k:H:P:j:T:I:a:p:d:e:s:R:r:u:f:C:J:K:S::m:M:NE", longopts, &option);
     if (c == -1 || c == EOF || c == 1)
       break;
 
@@ -962,6 +975,26 @@ process_arguments (int argc, char **argv)
     case 'N': /* no-body */
       no_body = TRUE;
       break;
+    case 'M': /* max-age */
+    {
+      int L = strlen(optarg);
+      if (L && optarg[L-1] == 'm')
+        maximum_age = atoi (optarg) * 60;
+      else if (L && optarg[L-1] == 'h')
+        maximum_age = atoi (optarg) * 60 * 60;
+      else if (L && optarg[L-1] == 'd')
+        maximum_age = atoi (optarg) * 60 * 60 * 24;
+      else if (L && (optarg[L-1] == 's' ||
+                     isdigit (optarg[L-1])))
+        maximum_age = atoi (optarg);
+      else {
+        fprintf (stderr, "unparsable max-age: %s\n", optarg);
+        exit (STATE_WARNING);
+      }
+      if (verbose >= 2)
+        printf ("* Maximal age of document set to %d seconds\n", maximum_age);
+    }
+    break;
     case 'E': /* show extended perfdata */
       show_extended_perfdata = TRUE;
       break;
@@ -1090,6 +1123,9 @@ print_help (void)
   printf (" %s\n", "-N, --no-body");
   printf ("    %s\n", _("Don't wait for document body: stop reading after headers."));
   printf ("    %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)"));
+  printf (" %s\n", "-M, --max-age=SECONDS");
+  printf ("    %s\n", _("Warn if document is more than SECONDS old. the number can also be of"));
+  printf ("    %s\n", _("the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days."));
   printf (" %s\n", "-T, --content-type=STRING");
   printf ("    %s\n", _("specify Content-Type header media type when POSTing\n"));
   printf (" %s\n", "-l, --linespan");
@@ -1181,7 +1217,7 @@ print_usage (void)
   printf ("       [-w <warn time>] [-c <critical time>] [-t <timeout>] [-E] [-a auth]\n");
   printf ("       [-f <ok|warning|critcal|follow>]\n");
   printf ("       [-e <expect>] [-d string] [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n");
-  printf ("       [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N]\n");
+  printf ("       [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>]\n");
   printf ("       [-A string] [-k string] [-S <version>] [--sni] [-C <warn_age>[,<crit_age>]]\n");
   printf ("       [-T <content-type>] [-j method]\n", progname);
   printf ("\n");
@@ -1351,7 +1387,164 @@ remove_newlines (char *s)
       *p = ' ';
 }
 
-char *perfd_time_ssl (double elapsed_time_ssl)
+char *
+perfd_time_ssl (double elapsed_time_ssl)
 {
   return fperfdata ("time_ssl", elapsed_time_ssl, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
 }
+
+char *
+get_header_value (const struct phr_header* headers, const size_t nof_headers, const char* header)
+{
+  for( int i = 0; i < nof_headers; i++ ) {
+    if( strncasecmp( header, headers[i].name, max( headers[i].name_len, 4 ) ) == 0 ) {
+      return strndup( headers[i].value, headers[i].value_len );
+    }
+  }
+  return NULL;
+}
+
+static time_t
+parse_time_string (const char *string)
+{
+  struct tm tm;
+  time_t t;
+  memset (&tm, 0, sizeof(tm));
+
+  /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
+
+  if (isupper (string[0])  &&  /* Tue */
+    islower (string[1])  &&
+    islower (string[2])  &&
+    ',' ==   string[3]   &&
+    ' ' ==   string[4]   &&
+    (isdigit(string[5]) || string[5] == ' ') &&   /* 25 */
+    isdigit (string[6])  &&
+    ' ' ==   string[7]   &&
+    isupper (string[8])  &&  /* Dec */
+    islower (string[9])  &&
+    islower (string[10]) &&
+    ' ' ==   string[11]  &&
+    isdigit (string[12]) &&  /* 2001 */
+    isdigit (string[13]) &&
+    isdigit (string[14]) &&
+    isdigit (string[15]) &&
+    ' ' ==   string[16]  &&
+    isdigit (string[17]) &&  /* 02: */
+    isdigit (string[18]) &&
+    ':' ==   string[19]  &&
+    isdigit (string[20]) &&  /* 59: */
+    isdigit (string[21]) &&
+    ':' ==   string[22]  &&
+    isdigit (string[23]) &&  /* 03 */
+    isdigit (string[24]) &&
+    ' ' ==   string[25]  &&
+    'G' ==   string[26]  &&  /* GMT */
+    'M' ==   string[27]  &&  /* GMT */
+    'T' ==   string[28]) {
+
+    tm.tm_sec  = 10 * (string[23]-'0') + (string[24]-'0');
+    tm.tm_min  = 10 * (string[20]-'0') + (string[21]-'0');
+    tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
+    tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
+    tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
+      !strncmp (string+8, "Feb", 3) ? 1 :
+      !strncmp (string+8, "Mar", 3) ? 2 :
+      !strncmp (string+8, "Apr", 3) ? 3 :
+      !strncmp (string+8, "May", 3) ? 4 :
+      !strncmp (string+8, "Jun", 3) ? 5 :
+      !strncmp (string+8, "Jul", 3) ? 6 :
+      !strncmp (string+8, "Aug", 3) ? 7 :
+      !strncmp (string+8, "Sep", 3) ? 8 :
+      !strncmp (string+8, "Oct", 3) ? 9 :
+      !strncmp (string+8, "Nov", 3) ? 10 :
+      !strncmp (string+8, "Dec", 3) ? 11 :
+      -1);
+    tm.tm_year = ((1000 * (string[12]-'0') +
+      100 * (string[13]-'0') +
+      10 * (string[14]-'0') +
+      (string[15]-'0'))
+      - 1900);
+
+    tm.tm_isdst = 0;  /* GMT is never in DST, right? */
+
+    if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
+      return 0;
+
+    /*
+    This is actually wrong: we need to subtract the local timezone
+    offset from GMT from this value.  But, that's ok in this usage,
+    because we only comparing these two GMT dates against each other,
+    so it doesn't matter what time zone we parse them in.
+    */
+
+    t = mktime (&tm);
+    if (t == (time_t) -1) t = 0;
+
+    if (verbose) {
+      const char *s = string;
+      while (*s && *s != '\r' && *s != '\n')
+      fputc (*s++, stdout);
+      printf (" ==> %lu\n", (unsigned long) t);
+    }
+
+    return t;
+
+  } else {
+    return 0;
+  }
+}
+
+int 
+check_document_dates (const curlhelp_curlbuf *header_buf, char (*msg)[DEFAULT_BUFFER_SIZE])
+{
+  char *server_date = NULL;
+  char *document_date = NULL;
+  int date_result = STATE_OK;
+  curlhelp_statusline status_line;
+  struct phr_header headers[255];
+  size_t nof_headers = 255;
+  size_t msglen;
+    
+  int res = phr_parse_response (header_buf->buf, header_buf->buflen,
+    &status_line.http_minor, &status_line.http_code, &status_line.msg, &msglen,
+    headers, &nof_headers, 0);
+  
+  server_date = get_header_value (headers, nof_headers, "date");
+  document_date = get_header_value (headers, nof_headers, "last-modified");
+
+  if (!server_date || !*server_date) {
+    snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sServer date unknown, "), *msg);
+    date_result = max_state_alt(STATE_UNKNOWN, date_result);
+  } else if (!document_date || !*document_date) {
+    snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sDocument modification date unknown, "), *msg);
+    date_result = max_state_alt(STATE_CRITICAL, date_result);
+  } else {
+    time_t srv_data = parse_time_string (server_date);
+    time_t doc_data = parse_time_string (document_date);
+    if (srv_data <= 0) {
+      snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sServer date \"%100s\" unparsable, "), *msg, server_date);
+      date_result = max_state_alt(STATE_CRITICAL, date_result);
+    } else if (doc_data <= 0) {
+      snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sDocument date \"%100s\" unparsable, "), *msg, document_date);
+      date_result = max_state_alt(STATE_CRITICAL, date_result);
+    } else if (doc_data > srv_data + 30) {
+      snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sDocument is %d seconds in the future, "), *msg, (int)doc_data - (int)srv_data);
+      date_result = max_state_alt(STATE_CRITICAL, date_result);
+    } else if (doc_data < srv_data - maximum_age) {
+      int n = (srv_data - doc_data);
+      if (n > (60 * 60 * 24 * 2)) {
+        snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sLast modified %.1f days ago, "), *msg, ((float) n) / (60 * 60 * 24));
+        date_result = max_state_alt(STATE_CRITICAL, date_result);
+      } else {
+        snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sLast modified %d:%02d:%02d ago, "), *msg, n / (60 * 60), (n / 60) % 60, n % 60);
+        date_result = max_state_alt(STATE_CRITICAL, date_result);
+      }
+    }
+  }
+  
+  if (server_date) free (server_date);
+  if (document_date) free (document_date);
+
+  return date_result;
+}
diff --git a/plugins/picohttpparser.c b/plugins/picohttpparser.c
new file mode 100644
index 0000000..6a2d872
--- /dev/null
+++ b/plugins/picohttpparser.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
+ *                         Shigeo Mitsunari
+ *
+ * The software is licensed under either the MIT License (below) or the Perl
+ * license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef __SSE4_2__
+#ifdef _MSC_VER
+#include <nmmintrin.h>
+#else
+#include <x86intrin.h>
+#endif
+#endif
+#include "picohttpparser.h"
+
+/* $Id: a707070d11d499609f99d09f97535642cec910a8 $ */
+
+#if __GNUC__ >= 3
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#else
+#define likely(x) (x)
+#define unlikely(x) (x)
+#endif
+
+#ifdef _MSC_VER
+#define ALIGNED(n) _declspec(align(n))
+#else
+#define ALIGNED(n) __attribute__((aligned(n)))
+#endif
+
+#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
+
+#define CHECK_EOF()                                                                                                                \
+    if (buf == buf_end) {                                                                                                          \
+        *ret = -2;                                                                                                                 \
+        return NULL;                                                                                                               \
+    }
+
+#define EXPECT_CHAR_NO_CHECK(ch)                                                                                                   \
+    if (*buf++ != ch) {                                                                                                            \
+        *ret = -1;                                                                                                                 \
+        return NULL;                                                                                                               \
+    }
+
+#define EXPECT_CHAR(ch)                                                                                                            \
+    CHECK_EOF();                                                                                                                   \
+    EXPECT_CHAR_NO_CHECK(ch);
+
+#define ADVANCE_TOKEN(tok, toklen)                                                                                                 \
+    do {                                                                                                                           \
+        const char *tok_start = buf;                                                                                               \
+        static const char ALIGNED(16) ranges2[] = "\000\040\177\177";                                                              \
+        int found2;                                                                                                                \
+        buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2);                                                  \
+        if (!found2) {                                                                                                             \
+            CHECK_EOF();                                                                                                           \
+        }                                                                                                                          \
+        while (1) {                                                                                                                \
+            if (*buf == ' ') {                                                                                                     \
+                break;                                                                                                             \
+            } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {                                                                      \
+                if ((unsigned char)*buf < '\040' || *buf == '\177') {                                                              \
+                    *ret = -1;                                                                                                     \
+                    return NULL;                                                                                                   \
+                }                                                                                                                  \
+            }                                                                                                                      \
+            ++buf;                                                                                                                 \
+            CHECK_EOF();                                                                                                           \
+        }                                                                                                                          \
+        tok = tok_start;                                                                                                           \
+        toklen = buf - tok_start;                                                                                                  \
+    } while (0)
+
+static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+                                    "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
+                                    "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
+                                    "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
+                                    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+                                    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+                                    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+                                    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
+{
+    *found = 0;
+#if __SSE4_2__
+    if (likely(buf_end - buf >= 16)) {
+        __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
+
+        size_t left = (buf_end - buf) & ~15;
+        do {
+            __m128i b16 = _mm_loadu_si128((const __m128i *)buf);
+            int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
+            if (unlikely(r != 16)) {
+                buf += r;
+                *found = 1;
+                break;
+            }
+            buf += 16;
+            left -= 16;
+        } while (likely(left != 0));
+    }
+#else
+    /* suppress unused parameter warning */
+    (void)buf_end;
+    (void)ranges;
+    (void)ranges_size;
+#endif
+    return buf;
+}
+
+static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
+{
+    const char *token_start = buf;
+
+#ifdef __SSE4_2__
+    static const char ranges1[] = "\0\010"
+                                  /* allow HT */
+                                  "\012\037"
+                                  /* allow SP and up to but not including DEL */
+                                  "\177\177"
+        /* allow chars w. MSB set */
+        ;
+    int found;
+    buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found);
+    if (found)
+        goto FOUND_CTL;
+#else
+    /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
+    while (likely(buf_end - buf >= 8)) {
+#define DOIT()                                                                                                                     \
+    do {                                                                                                                           \
+        if (unlikely(!IS_PRINTABLE_ASCII(*buf)))                                                                                   \
+            goto NonPrintable;                                                                                                     \
+        ++buf;                                                                                                                     \
+    } while (0)
+        DOIT();
+        DOIT();
+        DOIT();
+        DOIT();
+        DOIT();
+        DOIT();
+        DOIT();
+        DOIT();
+#undef DOIT
+        continue;
+    NonPrintable:
+        if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
+            goto FOUND_CTL;
+        }
+        ++buf;
+    }
+#endif
+    for (;; ++buf) {
+        CHECK_EOF();
+        if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
+            if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
+                goto FOUND_CTL;
+            }
+        }
+    }
+FOUND_CTL:
+    if (likely(*buf == '\015')) {
+        ++buf;
+        EXPECT_CHAR('\012');
+        *token_len = buf - 2 - token_start;
+    } else if (*buf == '\012') {
+        *token_len = buf - token_start;
+        ++buf;
+    } else {
+        *ret = -1;
+        return NULL;
+    }
+    *token = token_start;
+
+    return buf;
+}
+
+static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
+{
+    int ret_cnt = 0;
+    buf = last_len < 3 ? buf : buf + last_len - 3;
+
+    while (1) {
+        CHECK_EOF();
+        if (*buf == '\015') {
+            ++buf;
+            CHECK_EOF();
+            EXPECT_CHAR('\012');
+            ++ret_cnt;
+        } else if (*buf == '\012') {
+            ++buf;
+            ++ret_cnt;
+        } else {
+            ++buf;
+            ret_cnt = 0;
+        }
+        if (ret_cnt == 2) {
+            return buf;
+        }
+    }
+
+    *ret = -2;
+    return NULL;
+}
+
+#define PARSE_INT(valp_, mul_)                                                                                                     \
+    if (*buf < '0' || '9' < *buf) {                                                                                                \
+        buf++;                                                                                                                     \
+        *ret = -1;                                                                                                                 \
+        return NULL;                                                                                                               \
+    }                                                                                                                              \
+    *(valp_) = (mul_) * (*buf++ - '0');
+
+#define PARSE_INT_3(valp_)                                                                                                         \
+    do {                                                                                                                           \
+        int res_ = 0;                                                                                                              \
+        PARSE_INT(&res_, 100)                                                                                                      \
+        *valp_ = res_;                                                                                                             \
+        PARSE_INT(&res_, 10)                                                                                                       \
+        *valp_ += res_;                                                                                                            \
+        PARSE_INT(&res_, 1)                                                                                                        \
+        *valp_ += res_;                                                                                                            \
+    } while (0)
+
+/* returned pointer is always within [buf, buf_end), or null */
+static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
+{
+    /* we want at least [HTTP/1.<two chars>] to try to parse */
+    if (buf_end - buf < 9) {
+        *ret = -2;
+        return NULL;
+    }
+    EXPECT_CHAR_NO_CHECK('H');
+    EXPECT_CHAR_NO_CHECK('T');
+    EXPECT_CHAR_NO_CHECK('T');
+    EXPECT_CHAR_NO_CHECK('P');
+    EXPECT_CHAR_NO_CHECK('/');
+    EXPECT_CHAR_NO_CHECK('1');
+    EXPECT_CHAR_NO_CHECK('.');
+    PARSE_INT(minor_version, 1);
+    return buf;
+}
+
+static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
+                                 size_t max_headers, int *ret)
+{
+    for (;; ++*num_headers) {
+        CHECK_EOF();
+        if (*buf == '\015') {
+            ++buf;
+            EXPECT_CHAR('\012');
+            break;
+        } else if (*buf == '\012') {
+            ++buf;
+            break;
+        }
+        if (*num_headers == max_headers) {
+            *ret = -1;
+            return NULL;
+        }
+        if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
+            /* parsing name, but do not discard SP before colon, see
+             * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
+            headers[*num_headers].name = buf;
+            static const char ALIGNED(16) ranges1[] = "\x00 "  /* control chars and up to SP */
+                                                      "\"\""   /* 0x22 */
+                                                      "()"     /* 0x28,0x29 */
+                                                      ",,"     /* 0x2c */
+                                                      "//"     /* 0x2f */
+                                                      ":@"     /* 0x3a-0x40 */
+                                                      "[]"     /* 0x5b-0x5d */
+                                                      "{\377"; /* 0x7b-0xff */
+            int found;
+            buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found);
+            if (!found) {
+                CHECK_EOF();
+            }
+            while (1) {
+                if (*buf == ':') {
+                    break;
+                } else if (!token_char_map[(unsigned char)*buf]) {
+                    *ret = -1;
+                    return NULL;
+                }
+                ++buf;
+                CHECK_EOF();
+            }
+            if ((headers[*num_headers].name_len = buf - headers[*num_headers].name) == 0) {
+                *ret = -1;
+                return NULL;
+            }
+            ++buf;
+            for (;; ++buf) {
+                CHECK_EOF();
+                if (!(*buf == ' ' || *buf == '\t')) {
+                    break;
+                }
+            }
+        } else {
+            headers[*num_headers].name = NULL;
+            headers[*num_headers].name_len = 0;
+        }
+        if ((buf = get_token_to_eol(buf, buf_end, &headers[*num_headers].value, &headers[*num_headers].value_len, ret)) == NULL) {
+            return NULL;
+        }
+    }
+    return buf;
+}
+
+static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
+                                 size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
+                                 size_t max_headers, int *ret)
+{
+    /* skip first empty line (some clients add CRLF after POST content) */
+    CHECK_EOF();
+    if (*buf == '\015') {
+        ++buf;
+        EXPECT_CHAR('\012');
+    } else if (*buf == '\012') {
+        ++buf;
+    }
+
+    /* parse request line */
+    ADVANCE_TOKEN(*method, *method_len);
+    ++buf;
+    ADVANCE_TOKEN(*path, *path_len);
+    ++buf;
+    if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
+        return NULL;
+    }
+    if (*buf == '\015') {
+        ++buf;
+        EXPECT_CHAR('\012');
+    } else if (*buf == '\012') {
+        ++buf;
+    } else {
+        *ret = -1;
+        return NULL;
+    }
+
+    return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
+}
+
+int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
+                      size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
+{
+    const char *buf = buf_start, *buf_end = buf_start + len;
+    size_t max_headers = *num_headers;
+    int r;
+
+    *method = NULL;
+    *method_len = 0;
+    *path = NULL;
+    *path_len = 0;
+    *minor_version = -1;
+    *num_headers = 0;
+
+    /* if last_len != 0, check if the request is complete (a fast countermeasure
+       againt slowloris */
+    if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
+        return r;
+    }
+
+    if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
+                             &r)) == NULL) {
+        return r;
+    }
+
+    return (int)(buf - buf_start);
+}
+
+static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
+                                  size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
+{
+    /* parse "HTTP/1.x" */
+    if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
+        return NULL;
+    }
+    /* skip space */
+    if (*buf++ != ' ') {
+        *ret = -1;
+        return NULL;
+    }
+    /* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
+    if (buf_end - buf < 4) {
+        *ret = -2;
+        return NULL;
+    }
+    PARSE_INT_3(status);
+
+    /* skip space */
+    if (*buf++ != ' ') {
+        *ret = -1;
+        return NULL;
+    }
+    /* get message */
+    if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
+        return NULL;
+    }
+
+    return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
+}
+
+int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
+                       struct phr_header *headers, size_t *num_headers, size_t last_len)
+{
+    const char *buf = buf_start, *buf_end = buf + len;
+    size_t max_headers = *num_headers;
+    int r;
+
+    *minor_version = -1;
+    *status = 0;
+    *msg = NULL;
+    *msg_len = 0;
+    *num_headers = 0;
+
+    /* if last_len != 0, check if the response is complete (a fast countermeasure
+       against slowloris */
+    if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
+        return r;
+    }
+
+    if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
+        return r;
+    }
+
+    return (int)(buf - buf_start);
+}
+
+int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
+{
+    const char *buf = buf_start, *buf_end = buf + len;
+    size_t max_headers = *num_headers;
+    int r;
+
+    *num_headers = 0;
+
+    /* if last_len != 0, check if the response is complete (a fast countermeasure
+       against slowloris */
+    if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
+        return r;
+    }
+
+    if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
+        return r;
+    }
+
+    return (int)(buf - buf_start);
+}
+
+enum {
+    CHUNKED_IN_CHUNK_SIZE,
+    CHUNKED_IN_CHUNK_EXT,
+    CHUNKED_IN_CHUNK_DATA,
+    CHUNKED_IN_CHUNK_CRLF,
+    CHUNKED_IN_TRAILERS_LINE_HEAD,
+    CHUNKED_IN_TRAILERS_LINE_MIDDLE
+};
+
+static int decode_hex(int ch)
+{
+    if ('0' <= ch && ch <= '9') {
+        return ch - '0';
+    } else if ('A' <= ch && ch <= 'F') {
+        return ch - 'A' + 0xa;
+    } else if ('a' <= ch && ch <= 'f') {
+        return ch - 'a' + 0xa;
+    } else {
+        return -1;
+    }
+}
+
+ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
+{
+    size_t dst = 0, src = 0, bufsz = *_bufsz;
+    ssize_t ret = -2; /* incomplete */
+
+    while (1) {
+        switch (decoder->_state) {
+        case CHUNKED_IN_CHUNK_SIZE:
+            for (;; ++src) {
+                int v;
+                if (src == bufsz)
+                    goto Exit;
+                if ((v = decode_hex(buf[src])) == -1) {
+                    if (decoder->_hex_count == 0) {
+                        ret = -1;
+                        goto Exit;
+                    }
+                    break;
+                }
+                if (decoder->_hex_count == sizeof(size_t) * 2) {
+                    ret = -1;
+                    goto Exit;
+                }
+                decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
+                ++decoder->_hex_count;
+            }
+            decoder->_hex_count = 0;
+            decoder->_state = CHUNKED_IN_CHUNK_EXT;
+        /* fallthru */
+        case CHUNKED_IN_CHUNK_EXT:
+            /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
+            for (;; ++src) {
+                if (src == bufsz)
+                    goto Exit;
+                if (buf[src] == '\012')
+                    break;
+            }
+            ++src;
+            if (decoder->bytes_left_in_chunk == 0) {
+                if (decoder->consume_trailer) {
+                    decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
+                    break;
+                } else {
+                    goto Complete;
+                }
+            }
+            decoder->_state = CHUNKED_IN_CHUNK_DATA;
+        /* fallthru */
+        case CHUNKED_IN_CHUNK_DATA: {
+            size_t avail = bufsz - src;
+            if (avail < decoder->bytes_left_in_chunk) {
+                if (dst != src)
+                    memmove(buf + dst, buf + src, avail);
+                src += avail;
+                dst += avail;
+                decoder->bytes_left_in_chunk -= avail;
+                goto Exit;
+            }
+            if (dst != src)
+                memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
+            src += decoder->bytes_left_in_chunk;
+            dst += decoder->bytes_left_in_chunk;
+            decoder->bytes_left_in_chunk = 0;
+            decoder->_state = CHUNKED_IN_CHUNK_CRLF;
+        }
+        /* fallthru */
+        case CHUNKED_IN_CHUNK_CRLF:
+            for (;; ++src) {
+                if (src == bufsz)
+                    goto Exit;
+                if (buf[src] != '\015')
+                    break;
+            }
+            if (buf[src] != '\012') {
+                ret = -1;
+                goto Exit;
+            }
+            ++src;
+            decoder->_state = CHUNKED_IN_CHUNK_SIZE;
+            break;
+        case CHUNKED_IN_TRAILERS_LINE_HEAD:
+            for (;; ++src) {
+                if (src == bufsz)
+                    goto Exit;
+                if (buf[src] != '\015')
+                    break;
+            }
+            if (buf[src++] == '\012')
+                goto Complete;
+            decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
+        /* fallthru */
+        case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
+            for (;; ++src) {
+                if (src == bufsz)
+                    goto Exit;
+                if (buf[src] == '\012')
+                    break;
+            }
+            ++src;
+            decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
+            break;
+        default:
+            assert(!"decoder is corrupt");
+        }
+    }
+
+Complete:
+    ret = bufsz - src;
+Exit:
+    if (dst != src)
+        memmove(buf + dst, buf + src, bufsz - src);
+    *_bufsz = dst;
+    return ret;
+}
+
+int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
+{
+    return decoder->_state == CHUNKED_IN_CHUNK_DATA;
+}
+
+#undef CHECK_EOF
+#undef EXPECT_CHAR
+#undef ADVANCE_TOKEN
diff --git a/plugins/picohttpparser.h b/plugins/picohttpparser.h
new file mode 100644
index 0000000..a8fad71
--- /dev/null
+++ b/plugins/picohttpparser.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
+ *                         Shigeo Mitsunari
+ *
+ * The software is licensed under either the MIT License (below) or the Perl
+ * license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef picohttpparser_h
+#define picohttpparser_h
+
+#include <sys/types.h>
+
+#ifdef _MSC_VER
+#define ssize_t intptr_t
+#endif
+
+/* $Id: 67fd3ee74103ada60258d8a16e868f483abcca87 $ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* contains name and value of a header (name == NULL if is a continuing line
+ * of a multiline header */
+struct phr_header {
+    const char *name;
+    size_t name_len;
+    const char *value;
+    size_t value_len;
+};
+
+/* returns number of bytes consumed if successful, -2 if request is partial,
+ * -1 if failed */
+int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
+                      int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
+
+/* ditto */
+int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
+                       struct phr_header *headers, size_t *num_headers, size_t last_len);
+
+/* ditto */
+int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
+
+/* should be zero-filled before start */
+struct phr_chunked_decoder {
+    size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
+    char consume_trailer;       /* if trailing headers should be consumed */
+    char _hex_count;
+    char _state;
+};
+
+/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
+ * encoding headers.  When the function returns without an error, bufsz is
+ * updated to the length of the decoded data available.  Applications should
+ * repeatedly call the function while it returns -2 (incomplete) every time
+ * supplying newly arrived data.  If the end of the chunked-encoded data is
+ * found, the function returns a non-negative number indicating the number of
+ * octets left undecoded at the tail of the supplied buffer.  Returns -1 on
+ * error.
+ */
+ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
+
+/* returns if the chunked decoder is in middle of chunked data */
+int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif



More information about the Commits mailing list