summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2017-04-14 21:10:40 +0200
committerSven Nierlein <sven@nierlein.de>2018-10-22 16:30:31 +0200
commit9960c56e5e09caf33a8010e52cb32a931fb3ab2a (patch)
tree584c3efc2dc5b7f6870894ec7f4cac0662d51753
parentbbec77c7ecbc6ecaac06d2c8845f83a22546f273 (diff)
downloadmonitoring-plugins-9960c56e5e09caf33a8010e52cb32a931fb3ab2a.tar.gz
added -M<m> age option for document age, using picohttpparser from h2o (maybe handy
later to make a more robust header condition checker?)
-rw-r--r--.gitignore1
-rw-r--r--plugins/Makefile.am6
-rw-r--r--plugins/check_curl.c203
-rw-r--r--plugins/picohttpparser.c620
-rw-r--r--plugins/picohttpparser.h89
5 files changed, 912 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index 91de2c7d..b238aef6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -202,6 +202,7 @@ NP-VERSION-FILE
202/plugins/negate 202/plugins/negate
203/plugins/stamp-h* 203/plugins/stamp-h*
204/plugins/urlize 204/plugins/urlize
205/plugins/libpicohttpparser.a
205 206
206# /plugins/t/ 207# /plugins/t/
207/plugins/t/*.tmp 208/plugins/t/*.tmp
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index ffd8baf2..ff745c39 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -44,11 +44,13 @@ EXTRA_DIST = t tests
44 44
45PLUGINHDRS = common.h 45PLUGINHDRS = common.h
46 46
47noinst_LIBRARIES = libnpcommon.a 47noinst_LIBRARIES = libnpcommon.a libpicohttpparser.a
48 48
49libnpcommon_a_SOURCES = utils.c netutils.c sslutils.c runcmd.c \ 49libnpcommon_a_SOURCES = utils.c netutils.c sslutils.c runcmd.c \
50 popen.c utils.h netutils.h popen.h common.h runcmd.c runcmd.h 50 popen.c utils.h netutils.h popen.h common.h runcmd.c runcmd.h
51 51
52libpicohttpparser_a_SOURCES = picohttpparser.c
53
52BASEOBJS = libnpcommon.a ../lib/libmonitoringplug.a ../gl/libgnu.a 54BASEOBJS = libnpcommon.a ../lib/libmonitoringplug.a ../gl/libgnu.a
53NETOBJS = $(BASEOBJS) $(EXTRA_NETOBLS) 55NETOBJS = $(BASEOBJS) $(EXTRA_NETOBLS)
54NETLIBS = $(NETOBJS) $(SOCKETLIBS) 56NETLIBS = $(NETOBJS) $(SOCKETLIBS)
@@ -71,7 +73,7 @@ check_apt_LDADD = $(BASEOBJS)
71check_cluster_LDADD = $(BASEOBJS) 73check_cluster_LDADD = $(BASEOBJS)
72check_curl_CFLAGS = $(AM_CFLAGS) $(LIBCURLCFLAGS) 74check_curl_CFLAGS = $(AM_CFLAGS) $(LIBCURLCFLAGS)
73check_curl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCURLINCLUDE) 75check_curl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCURLINCLUDE)
74check_curl_LDADD = $(NETLIBS) $(LIBCURLLIBS) $(SSLOBJS) 76check_curl_LDADD = $(NETLIBS) $(LIBCURLLIBS) $(SSLOBJS) libpicohttpparser.a
75check_dbi_LDADD = $(NETLIBS) $(DBILIBS) 77check_dbi_LDADD = $(NETLIBS) $(DBILIBS)
76check_dig_LDADD = $(NETLIBS) 78check_dig_LDADD = $(NETLIBS)
77check_disk_LDADD = $(BASEOBJS) 79check_disk_LDADD = $(BASEOBJS)
diff --git a/plugins/check_curl.c b/plugins/check_curl.c
index 4129acca..7576b2df 100644
--- a/plugins/check_curl.c
+++ b/plugins/check_curl.c
@@ -37,6 +37,8 @@ const char *progname = "check_curl";
37const char *copyright = "2006-2017"; 37const char *copyright = "2006-2017";
38const char *email = "devel@monitoring-plugins.org"; 38const char *email = "devel@monitoring-plugins.org";
39 39
40#include <ctype.h>
41
40#include "common.h" 42#include "common.h"
41#include "utils.h" 43#include "utils.h"
42 44
@@ -47,6 +49,8 @@ const char *email = "devel@monitoring-plugins.org";
47#include "curl/curl.h" 49#include "curl/curl.h"
48#include "curl/easy.h" 50#include "curl/easy.h"
49 51
52#include "picohttpparser.h"
53
50#define MAKE_LIBCURL_VERSION(major, minor, patch) ((major)*0x10000 + (minor)*0x100 + (patch)) 54#define MAKE_LIBCURL_VERSION(major, minor, patch) ((major)*0x10000 + (minor)*0x100 + (patch))
51 55
52#define DEFAULT_BUFFER_SIZE 2048 56#define DEFAULT_BUFFER_SIZE 2048
@@ -73,7 +77,7 @@ typedef struct {
73 int http_code; /* HTTP return code as in RFC 2145 */ 77 int http_code; /* HTTP return code as in RFC 2145 */
74 int http_subcode; /* Microsoft IIS extension, HTTP subcodes, see 78 int http_subcode; /* Microsoft IIS extension, HTTP subcodes, see
75 * http://support.microsoft.com/kb/318380/en-us */ 79 * http://support.microsoft.com/kb/318380/en-us */
76 char *msg; /* the human readable message */ 80 const char *msg; /* the human readable message */
77 char *first_line; /* a copy of the first line */ 81 char *first_line; /* a copy of the first line */
78} curlhelp_statusline; 82} curlhelp_statusline;
79 83
@@ -142,6 +146,7 @@ char *client_privkey = NULL;
142char *ca_cert = NULL; 146char *ca_cert = NULL;
143X509 *cert = NULL; 147X509 *cert = NULL;
144int no_body = FALSE; 148int no_body = FALSE;
149int maximum_age = -1;
145int address_family = AF_UNSPEC; 150int address_family = AF_UNSPEC;
146 151
147int process_arguments (int, char**); 152int process_arguments (int, char**);
@@ -156,6 +161,9 @@ void curlhelp_freebuffer (curlhelp_curlbuf*);
156int curlhelp_parse_statusline (const char*, curlhelp_statusline *); 161int curlhelp_parse_statusline (const char*, curlhelp_statusline *);
157void curlhelp_free_statusline (curlhelp_statusline *); 162void curlhelp_free_statusline (curlhelp_statusline *);
158char *perfd_time_ssl (double microsec); 163char *perfd_time_ssl (double microsec);
164char *get_header_value (const struct phr_header* headers, const size_t nof_headers, const char* header);
165static time_t parse_time_string (const char *string);
166int check_document_dates (const curlhelp_curlbuf *, char (*msg)[DEFAULT_BUFFER_SIZE]);
159 167
160void remove_newlines (char *); 168void remove_newlines (char *);
161void test_file (char *); 169void test_file (char *);
@@ -391,7 +399,7 @@ check_http (void)
391 /* Curl errors, result in critical Nagios state */ 399 /* Curl errors, result in critical Nagios state */
392 if (res != CURLE_OK) { 400 if (res != CURLE_OK) {
393 remove_newlines (errbuf); 401 remove_newlines (errbuf);
394 snprintf (msg, DEFAULT_BUFFER_SIZE, _("Invalid HTTP response received from host on port %d: cURL returned %d - %s\n"), 402 snprintf (msg, DEFAULT_BUFFER_SIZE, _("Invalid HTTP response received from host on port %d: cURL returned %d - %s"),
395 server_port, res, curl_easy_strerror(res)); 403 server_port, res, curl_easy_strerror(res));
396 die (STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg); 404 die (STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg);
397 } 405 }
@@ -506,6 +514,10 @@ check_http (void)
506 status_line.http_code, status_line.msg, code); 514 status_line.http_code, status_line.msg, code);
507 } 515 }
508 516
517 if (maximum_age >= 0) {
518 result = max_state_alt(check_document_dates(&header_buf, &msg), result);
519 }
520
509 /* Page and Header content checks go here */ 521 /* Page and Header content checks go here */
510 522
511 if (strlen (header_expect)) { 523 if (strlen (header_expect)) {
@@ -638,6 +650,7 @@ process_arguments (int argc, char **argv)
638 {"useragent", required_argument, 0, 'A'}, 650 {"useragent", required_argument, 0, 'A'},
639 {"header", required_argument, 0, 'k'}, 651 {"header", required_argument, 0, 'k'},
640 {"no-body", no_argument, 0, 'N'}, 652 {"no-body", no_argument, 0, 'N'},
653 {"max-age", required_argument, 0, 'M'},
641 {"content-type", required_argument, 0, 'T'}, 654 {"content-type", required_argument, 0, 'T'},
642 {"pagesize", required_argument, 0, 'm'}, 655 {"pagesize", required_argument, 0, 'm'},
643 {"invert-regex", no_argument, NULL, INVERT_REGEX}, 656 {"invert-regex", no_argument, NULL, INVERT_REGEX},
@@ -665,7 +678,7 @@ process_arguments (int argc, char **argv)
665 } 678 }
666 679
667 while (1) { 680 while (1) {
668 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); 681 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);
669 if (c == -1 || c == EOF || c == 1) 682 if (c == -1 || c == EOF || c == 1)
670 break; 683 break;
671 684
@@ -962,6 +975,26 @@ process_arguments (int argc, char **argv)
962 case 'N': /* no-body */ 975 case 'N': /* no-body */
963 no_body = TRUE; 976 no_body = TRUE;
964 break; 977 break;
978 case 'M': /* max-age */
979 {
980 int L = strlen(optarg);
981 if (L && optarg[L-1] == 'm')
982 maximum_age = atoi (optarg) * 60;
983 else if (L && optarg[L-1] == 'h')
984 maximum_age = atoi (optarg) * 60 * 60;
985 else if (L && optarg[L-1] == 'd')
986 maximum_age = atoi (optarg) * 60 * 60 * 24;
987 else if (L && (optarg[L-1] == 's' ||
988 isdigit (optarg[L-1])))
989 maximum_age = atoi (optarg);
990 else {
991 fprintf (stderr, "unparsable max-age: %s\n", optarg);
992 exit (STATE_WARNING);
993 }
994 if (verbose >= 2)
995 printf ("* Maximal age of document set to %d seconds\n", maximum_age);
996 }
997 break;
965 case 'E': /* show extended perfdata */ 998 case 'E': /* show extended perfdata */
966 show_extended_perfdata = TRUE; 999 show_extended_perfdata = TRUE;
967 break; 1000 break;
@@ -1090,6 +1123,9 @@ print_help (void)
1090 printf (" %s\n", "-N, --no-body"); 1123 printf (" %s\n", "-N, --no-body");
1091 printf (" %s\n", _("Don't wait for document body: stop reading after headers.")); 1124 printf (" %s\n", _("Don't wait for document body: stop reading after headers."));
1092 printf (" %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)")); 1125 printf (" %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)"));
1126 printf (" %s\n", "-M, --max-age=SECONDS");
1127 printf (" %s\n", _("Warn if document is more than SECONDS old. the number can also be of"));
1128 printf (" %s\n", _("the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days."));
1093 printf (" %s\n", "-T, --content-type=STRING"); 1129 printf (" %s\n", "-T, --content-type=STRING");
1094 printf (" %s\n", _("specify Content-Type header media type when POSTing\n")); 1130 printf (" %s\n", _("specify Content-Type header media type when POSTing\n"));
1095 printf (" %s\n", "-l, --linespan"); 1131 printf (" %s\n", "-l, --linespan");
@@ -1181,7 +1217,7 @@ print_usage (void)
1181 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-E] [-a auth]\n"); 1217 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-E] [-a auth]\n");
1182 printf (" [-f <ok|warning|critcal|follow>]\n"); 1218 printf (" [-f <ok|warning|critcal|follow>]\n");
1183 printf (" [-e <expect>] [-d string] [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n"); 1219 printf (" [-e <expect>] [-d string] [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n");
1184 printf (" [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N]\n"); 1220 printf (" [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>]\n");
1185 printf (" [-A string] [-k string] [-S <version>] [--sni] [-C <warn_age>[,<crit_age>]]\n"); 1221 printf (" [-A string] [-k string] [-S <version>] [--sni] [-C <warn_age>[,<crit_age>]]\n");
1186 printf (" [-T <content-type>] [-j method]\n", progname); 1222 printf (" [-T <content-type>] [-j method]\n", progname);
1187 printf ("\n"); 1223 printf ("\n");
@@ -1351,7 +1387,164 @@ remove_newlines (char *s)
1351 *p = ' '; 1387 *p = ' ';
1352} 1388}
1353 1389
1354char *perfd_time_ssl (double elapsed_time_ssl) 1390char *
1391perfd_time_ssl (double elapsed_time_ssl)
1355{ 1392{
1356 return fperfdata ("time_ssl", elapsed_time_ssl, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0); 1393 return fperfdata ("time_ssl", elapsed_time_ssl, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
1357} 1394}
1395
1396char *
1397get_header_value (const struct phr_header* headers, const size_t nof_headers, const char* header)
1398{
1399 for( int i = 0; i < nof_headers; i++ ) {
1400 if( strncasecmp( header, headers[i].name, max( headers[i].name_len, 4 ) ) == 0 ) {
1401 return strndup( headers[i].value, headers[i].value_len );
1402 }
1403 }
1404 return NULL;
1405}
1406
1407static time_t
1408parse_time_string (const char *string)
1409{
1410 struct tm tm;
1411 time_t t;
1412 memset (&tm, 0, sizeof(tm));
1413
1414 /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
1415
1416 if (isupper (string[0]) && /* Tue */
1417 islower (string[1]) &&
1418 islower (string[2]) &&
1419 ',' == string[3] &&
1420 ' ' == string[4] &&
1421 (isdigit(string[5]) || string[5] == ' ') && /* 25 */
1422 isdigit (string[6]) &&
1423 ' ' == string[7] &&
1424 isupper (string[8]) && /* Dec */
1425 islower (string[9]) &&
1426 islower (string[10]) &&
1427 ' ' == string[11] &&
1428 isdigit (string[12]) && /* 2001 */
1429 isdigit (string[13]) &&
1430 isdigit (string[14]) &&
1431 isdigit (string[15]) &&
1432 ' ' == string[16] &&
1433 isdigit (string[17]) && /* 02: */
1434 isdigit (string[18]) &&
1435 ':' == string[19] &&
1436 isdigit (string[20]) && /* 59: */
1437 isdigit (string[21]) &&
1438 ':' == string[22] &&
1439 isdigit (string[23]) && /* 03 */
1440 isdigit (string[24]) &&
1441 ' ' == string[25] &&
1442 'G' == string[26] && /* GMT */
1443 'M' == string[27] && /* GMT */
1444 'T' == string[28]) {
1445
1446 tm.tm_sec = 10 * (string[23]-'0') + (string[24]-'0');
1447 tm.tm_min = 10 * (string[20]-'0') + (string[21]-'0');
1448 tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
1449 tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
1450 tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
1451 !strncmp (string+8, "Feb", 3) ? 1 :
1452 !strncmp (string+8, "Mar", 3) ? 2 :
1453 !strncmp (string+8, "Apr", 3) ? 3 :
1454 !strncmp (string+8, "May", 3) ? 4 :
1455 !strncmp (string+8, "Jun", 3) ? 5 :
1456 !strncmp (string+8, "Jul", 3) ? 6 :
1457 !strncmp (string+8, "Aug", 3) ? 7 :
1458 !strncmp (string+8, "Sep", 3) ? 8 :
1459 !strncmp (string+8, "Oct", 3) ? 9 :
1460 !strncmp (string+8, "Nov", 3) ? 10 :
1461 !strncmp (string+8, "Dec", 3) ? 11 :
1462 -1);
1463 tm.tm_year = ((1000 * (string[12]-'0') +
1464 100 * (string[13]-'0') +
1465 10 * (string[14]-'0') +
1466 (string[15]-'0'))
1467 - 1900);
1468
1469 tm.tm_isdst = 0; /* GMT is never in DST, right? */
1470
1471 if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
1472 return 0;
1473
1474 /*
1475 This is actually wrong: we need to subtract the local timezone
1476 offset from GMT from this value. But, that's ok in this usage,
1477 because we only comparing these two GMT dates against each other,
1478 so it doesn't matter what time zone we parse them in.
1479 */
1480
1481 t = mktime (&tm);
1482 if (t == (time_t) -1) t = 0;
1483
1484 if (verbose) {
1485 const char *s = string;
1486 while (*s && *s != '\r' && *s != '\n')
1487 fputc (*s++, stdout);
1488 printf (" ==> %lu\n", (unsigned long) t);
1489 }
1490
1491 return t;
1492
1493 } else {
1494 return 0;
1495 }
1496}
1497
1498int
1499check_document_dates (const curlhelp_curlbuf *header_buf, char (*msg)[DEFAULT_BUFFER_SIZE])
1500{
1501 char *server_date = NULL;
1502 char *document_date = NULL;
1503 int date_result = STATE_OK;
1504 curlhelp_statusline status_line;
1505 struct phr_header headers[255];
1506 size_t nof_headers = 255;
1507 size_t msglen;
1508
1509 int res = phr_parse_response (header_buf->buf, header_buf->buflen,
1510 &status_line.http_minor, &status_line.http_code, &status_line.msg, &msglen,
1511 headers, &nof_headers, 0);
1512
1513 server_date = get_header_value (headers, nof_headers, "date");
1514 document_date = get_header_value (headers, nof_headers, "last-modified");
1515
1516 if (!server_date || !*server_date) {
1517 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sServer date unknown, "), *msg);
1518 date_result = max_state_alt(STATE_UNKNOWN, date_result);
1519 } else if (!document_date || !*document_date) {
1520 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sDocument modification date unknown, "), *msg);
1521 date_result = max_state_alt(STATE_CRITICAL, date_result);
1522 } else {
1523 time_t srv_data = parse_time_string (server_date);
1524 time_t doc_data = parse_time_string (document_date);
1525 if (srv_data <= 0) {
1526 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sServer date \"%100s\" unparsable, "), *msg, server_date);
1527 date_result = max_state_alt(STATE_CRITICAL, date_result);
1528 } else if (doc_data <= 0) {
1529 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sDocument date \"%100s\" unparsable, "), *msg, document_date);
1530 date_result = max_state_alt(STATE_CRITICAL, date_result);
1531 } else if (doc_data > srv_data + 30) {
1532 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sDocument is %d seconds in the future, "), *msg, (int)doc_data - (int)srv_data);
1533 date_result = max_state_alt(STATE_CRITICAL, date_result);
1534 } else if (doc_data < srv_data - maximum_age) {
1535 int n = (srv_data - doc_data);
1536 if (n > (60 * 60 * 24 * 2)) {
1537 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sLast modified %.1f days ago, "), *msg, ((float) n) / (60 * 60 * 24));
1538 date_result = max_state_alt(STATE_CRITICAL, date_result);
1539 } else {
1540 snprintf (*msg, DEFAULT_BUFFER_SIZE, _("%sLast modified %d:%02d:%02d ago, "), *msg, n / (60 * 60), (n / 60) % 60, n % 60);
1541 date_result = max_state_alt(STATE_CRITICAL, date_result);
1542 }
1543 }
1544 }
1545
1546 if (server_date) free (server_date);
1547 if (document_date) free (document_date);
1548
1549 return date_result;
1550}
diff --git a/plugins/picohttpparser.c b/plugins/picohttpparser.c
new file mode 100644
index 00000000..6a2d872d
--- /dev/null
+++ b/plugins/picohttpparser.c
@@ -0,0 +1,620 @@
1/*
2 * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
3 * Shigeo Mitsunari
4 *
5 * The software is licensed under either the MIT License (below) or the Perl
6 * license.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to
10 * deal in the Software without restriction, including without limitation the
11 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 * sell copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 * IN THE SOFTWARE.
25 */
26
27#include <assert.h>
28#include <stddef.h>
29#include <string.h>
30#ifdef __SSE4_2__
31#ifdef _MSC_VER
32#include <nmmintrin.h>
33#else
34#include <x86intrin.h>
35#endif
36#endif
37#include "picohttpparser.h"
38
39/* $Id: a707070d11d499609f99d09f97535642cec910a8 $ */
40
41#if __GNUC__ >= 3
42#define likely(x) __builtin_expect(!!(x), 1)
43#define unlikely(x) __builtin_expect(!!(x), 0)
44#else
45#define likely(x) (x)
46#define unlikely(x) (x)
47#endif
48
49#ifdef _MSC_VER
50#define ALIGNED(n) _declspec(align(n))
51#else
52#define ALIGNED(n) __attribute__((aligned(n)))
53#endif
54
55#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
56
57#define CHECK_EOF() \
58 if (buf == buf_end) { \
59 *ret = -2; \
60 return NULL; \
61 }
62
63#define EXPECT_CHAR_NO_CHECK(ch) \
64 if (*buf++ != ch) { \
65 *ret = -1; \
66 return NULL; \
67 }
68
69#define EXPECT_CHAR(ch) \
70 CHECK_EOF(); \
71 EXPECT_CHAR_NO_CHECK(ch);
72
73#define ADVANCE_TOKEN(tok, toklen) \
74 do { \
75 const char *tok_start = buf; \
76 static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \
77 int found2; \
78 buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \
79 if (!found2) { \
80 CHECK_EOF(); \
81 } \
82 while (1) { \
83 if (*buf == ' ') { \
84 break; \
85 } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
86 if ((unsigned char)*buf < '\040' || *buf == '\177') { \
87 *ret = -1; \
88 return NULL; \
89 } \
90 } \
91 ++buf; \
92 CHECK_EOF(); \
93 } \
94 tok = tok_start; \
95 toklen = buf - tok_start; \
96 } while (0)
97
98static 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"
99 "\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"
100 "\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"
101 "\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"
102 "\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"
103 "\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"
104 "\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"
105 "\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";
106
107static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
108{
109 *found = 0;
110#if __SSE4_2__
111 if (likely(buf_end - buf >= 16)) {
112 __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
113
114 size_t left = (buf_end - buf) & ~15;
115 do {
116 __m128i b16 = _mm_loadu_si128((const __m128i *)buf);
117 int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
118 if (unlikely(r != 16)) {
119 buf += r;
120 *found = 1;
121 break;
122 }
123 buf += 16;
124 left -= 16;
125 } while (likely(left != 0));
126 }
127#else
128 /* suppress unused parameter warning */
129 (void)buf_end;
130 (void)ranges;
131 (void)ranges_size;
132#endif
133 return buf;
134}
135
136static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
137{
138 const char *token_start = buf;
139
140#ifdef __SSE4_2__
141 static const char ranges1[] = "\0\010"
142 /* allow HT */
143 "\012\037"
144 /* allow SP and up to but not including DEL */
145 "\177\177"
146 /* allow chars w. MSB set */
147 ;
148 int found;
149 buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found);
150 if (found)
151 goto FOUND_CTL;
152#else
153 /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
154 while (likely(buf_end - buf >= 8)) {
155#define DOIT() \
156 do { \
157 if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
158 goto NonPrintable; \
159 ++buf; \
160 } while (0)
161 DOIT();
162 DOIT();
163 DOIT();
164 DOIT();
165 DOIT();
166 DOIT();
167 DOIT();
168 DOIT();
169#undef DOIT
170 continue;
171 NonPrintable:
172 if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
173 goto FOUND_CTL;
174 }
175 ++buf;
176 }
177#endif
178 for (;; ++buf) {
179 CHECK_EOF();
180 if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
181 if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
182 goto FOUND_CTL;
183 }
184 }
185 }
186FOUND_CTL:
187 if (likely(*buf == '\015')) {
188 ++buf;
189 EXPECT_CHAR('\012');
190 *token_len = buf - 2 - token_start;
191 } else if (*buf == '\012') {
192 *token_len = buf - token_start;
193 ++buf;
194 } else {
195 *ret = -1;
196 return NULL;
197 }
198 *token = token_start;
199
200 return buf;
201}
202
203static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
204{
205 int ret_cnt = 0;
206 buf = last_len < 3 ? buf : buf + last_len - 3;
207
208 while (1) {
209 CHECK_EOF();
210 if (*buf == '\015') {
211 ++buf;
212 CHECK_EOF();
213 EXPECT_CHAR('\012');
214 ++ret_cnt;
215 } else if (*buf == '\012') {
216 ++buf;
217 ++ret_cnt;
218 } else {
219 ++buf;
220 ret_cnt = 0;
221 }
222 if (ret_cnt == 2) {
223 return buf;
224 }
225 }
226
227 *ret = -2;
228 return NULL;
229}
230
231#define PARSE_INT(valp_, mul_) \
232 if (*buf < '0' || '9' < *buf) { \
233 buf++; \
234 *ret = -1; \
235 return NULL; \
236 } \
237 *(valp_) = (mul_) * (*buf++ - '0');
238
239#define PARSE_INT_3(valp_) \
240 do { \
241 int res_ = 0; \
242 PARSE_INT(&res_, 100) \
243 *valp_ = res_; \
244 PARSE_INT(&res_, 10) \
245 *valp_ += res_; \
246 PARSE_INT(&res_, 1) \
247 *valp_ += res_; \
248 } while (0)
249
250/* returned pointer is always within [buf, buf_end), or null */
251static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
252{
253 /* we want at least [HTTP/1.<two chars>] to try to parse */
254 if (buf_end - buf < 9) {
255 *ret = -2;
256 return NULL;
257 }
258 EXPECT_CHAR_NO_CHECK('H');
259 EXPECT_CHAR_NO_CHECK('T');
260 EXPECT_CHAR_NO_CHECK('T');
261 EXPECT_CHAR_NO_CHECK('P');
262 EXPECT_CHAR_NO_CHECK('/');
263 EXPECT_CHAR_NO_CHECK('1');
264 EXPECT_CHAR_NO_CHECK('.');
265 PARSE_INT(minor_version, 1);
266 return buf;
267}
268
269static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
270 size_t max_headers, int *ret)
271{
272 for (;; ++*num_headers) {
273 CHECK_EOF();
274 if (*buf == '\015') {
275 ++buf;
276 EXPECT_CHAR('\012');
277 break;
278 } else if (*buf == '\012') {
279 ++buf;
280 break;
281 }
282 if (*num_headers == max_headers) {
283 *ret = -1;
284 return NULL;
285 }
286 if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
287 /* parsing name, but do not discard SP before colon, see
288 * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
289 headers[*num_headers].name = buf;
290 static const char ALIGNED(16) ranges1[] = "\x00 " /* control chars and up to SP */
291 "\"\"" /* 0x22 */
292 "()" /* 0x28,0x29 */
293 ",," /* 0x2c */
294 "//" /* 0x2f */
295 ":@" /* 0x3a-0x40 */
296 "[]" /* 0x5b-0x5d */
297 "{\377"; /* 0x7b-0xff */
298 int found;
299 buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found);
300 if (!found) {
301 CHECK_EOF();
302 }
303 while (1) {
304 if (*buf == ':') {
305 break;
306 } else if (!token_char_map[(unsigned char)*buf]) {
307 *ret = -1;
308 return NULL;
309 }
310 ++buf;
311 CHECK_EOF();
312 }
313 if ((headers[*num_headers].name_len = buf - headers[*num_headers].name) == 0) {
314 *ret = -1;
315 return NULL;
316 }
317 ++buf;
318 for (;; ++buf) {
319 CHECK_EOF();
320 if (!(*buf == ' ' || *buf == '\t')) {
321 break;
322 }
323 }
324 } else {
325 headers[*num_headers].name = NULL;
326 headers[*num_headers].name_len = 0;
327 }
328 if ((buf = get_token_to_eol(buf, buf_end, &headers[*num_headers].value, &headers[*num_headers].value_len, ret)) == NULL) {
329 return NULL;
330 }
331 }
332 return buf;
333}
334
335static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
336 size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
337 size_t max_headers, int *ret)
338{
339 /* skip first empty line (some clients add CRLF after POST content) */
340 CHECK_EOF();
341 if (*buf == '\015') {
342 ++buf;
343 EXPECT_CHAR('\012');
344 } else if (*buf == '\012') {
345 ++buf;
346 }
347
348 /* parse request line */
349 ADVANCE_TOKEN(*method, *method_len);
350 ++buf;
351 ADVANCE_TOKEN(*path, *path_len);
352 ++buf;
353 if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
354 return NULL;
355 }
356 if (*buf == '\015') {
357 ++buf;
358 EXPECT_CHAR('\012');
359 } else if (*buf == '\012') {
360 ++buf;
361 } else {
362 *ret = -1;
363 return NULL;
364 }
365
366 return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
367}
368
369int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
370 size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
371{
372 const char *buf = buf_start, *buf_end = buf_start + len;
373 size_t max_headers = *num_headers;
374 int r;
375
376 *method = NULL;
377 *method_len = 0;
378 *path = NULL;
379 *path_len = 0;
380 *minor_version = -1;
381 *num_headers = 0;
382
383 /* if last_len != 0, check if the request is complete (a fast countermeasure
384 againt slowloris */
385 if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
386 return r;
387 }
388
389 if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
390 &r)) == NULL) {
391 return r;
392 }
393
394 return (int)(buf - buf_start);
395}
396
397static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
398 size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
399{
400 /* parse "HTTP/1.x" */
401 if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
402 return NULL;
403 }
404 /* skip space */
405 if (*buf++ != ' ') {
406 *ret = -1;
407 return NULL;
408 }
409 /* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
410 if (buf_end - buf < 4) {
411 *ret = -2;
412 return NULL;
413 }
414 PARSE_INT_3(status);
415
416 /* skip space */
417 if (*buf++ != ' ') {
418 *ret = -1;
419 return NULL;
420 }
421 /* get message */
422 if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
423 return NULL;
424 }
425
426 return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
427}
428
429int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
430 struct phr_header *headers, size_t *num_headers, size_t last_len)
431{
432 const char *buf = buf_start, *buf_end = buf + len;
433 size_t max_headers = *num_headers;
434 int r;
435
436 *minor_version = -1;
437 *status = 0;
438 *msg = NULL;
439 *msg_len = 0;
440 *num_headers = 0;
441
442 /* if last_len != 0, check if the response is complete (a fast countermeasure
443 against slowloris */
444 if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
445 return r;
446 }
447
448 if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
449 return r;
450 }
451
452 return (int)(buf - buf_start);
453}
454
455int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
456{
457 const char *buf = buf_start, *buf_end = buf + len;
458 size_t max_headers = *num_headers;
459 int r;
460
461 *num_headers = 0;
462
463 /* if last_len != 0, check if the response is complete (a fast countermeasure
464 against slowloris */
465 if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
466 return r;
467 }
468
469 if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
470 return r;
471 }
472
473 return (int)(buf - buf_start);
474}
475
476enum {
477 CHUNKED_IN_CHUNK_SIZE,
478 CHUNKED_IN_CHUNK_EXT,
479 CHUNKED_IN_CHUNK_DATA,
480 CHUNKED_IN_CHUNK_CRLF,
481 CHUNKED_IN_TRAILERS_LINE_HEAD,
482 CHUNKED_IN_TRAILERS_LINE_MIDDLE
483};
484
485static int decode_hex(int ch)
486{
487 if ('0' <= ch && ch <= '9') {
488 return ch - '0';
489 } else if ('A' <= ch && ch <= 'F') {
490 return ch - 'A' + 0xa;
491 } else if ('a' <= ch && ch <= 'f') {
492 return ch - 'a' + 0xa;
493 } else {
494 return -1;
495 }
496}
497
498ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
499{
500 size_t dst = 0, src = 0, bufsz = *_bufsz;
501 ssize_t ret = -2; /* incomplete */
502
503 while (1) {
504 switch (decoder->_state) {
505 case CHUNKED_IN_CHUNK_SIZE:
506 for (;; ++src) {
507 int v;
508 if (src == bufsz)
509 goto Exit;
510 if ((v = decode_hex(buf[src])) == -1) {
511 if (decoder->_hex_count == 0) {
512 ret = -1;
513 goto Exit;
514 }
515 break;
516 }
517 if (decoder->_hex_count == sizeof(size_t) * 2) {
518 ret = -1;
519 goto Exit;
520 }
521 decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
522 ++decoder->_hex_count;
523 }
524 decoder->_hex_count = 0;
525 decoder->_state = CHUNKED_IN_CHUNK_EXT;
526 /* fallthru */
527 case CHUNKED_IN_CHUNK_EXT:
528 /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
529 for (;; ++src) {
530 if (src == bufsz)
531 goto Exit;
532 if (buf[src] == '\012')
533 break;
534 }
535 ++src;
536 if (decoder->bytes_left_in_chunk == 0) {
537 if (decoder->consume_trailer) {
538 decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
539 break;
540 } else {
541 goto Complete;
542 }
543 }
544 decoder->_state = CHUNKED_IN_CHUNK_DATA;
545 /* fallthru */
546 case CHUNKED_IN_CHUNK_DATA: {
547 size_t avail = bufsz - src;
548 if (avail < decoder->bytes_left_in_chunk) {
549 if (dst != src)
550 memmove(buf + dst, buf + src, avail);
551 src += avail;
552 dst += avail;
553 decoder->bytes_left_in_chunk -= avail;
554 goto Exit;
555 }
556 if (dst != src)
557 memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
558 src += decoder->bytes_left_in_chunk;
559 dst += decoder->bytes_left_in_chunk;
560 decoder->bytes_left_in_chunk = 0;
561 decoder->_state = CHUNKED_IN_CHUNK_CRLF;
562 }
563 /* fallthru */
564 case CHUNKED_IN_CHUNK_CRLF:
565 for (;; ++src) {
566 if (src == bufsz)
567 goto Exit;
568 if (buf[src] != '\015')
569 break;
570 }
571 if (buf[src] != '\012') {
572 ret = -1;
573 goto Exit;
574 }
575 ++src;
576 decoder->_state = CHUNKED_IN_CHUNK_SIZE;
577 break;
578 case CHUNKED_IN_TRAILERS_LINE_HEAD:
579 for (;; ++src) {
580 if (src == bufsz)
581 goto Exit;
582 if (buf[src] != '\015')
583 break;
584 }
585 if (buf[src++] == '\012')
586 goto Complete;
587 decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
588 /* fallthru */
589 case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
590 for (;; ++src) {
591 if (src == bufsz)
592 goto Exit;
593 if (buf[src] == '\012')
594 break;
595 }
596 ++src;
597 decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
598 break;
599 default:
600 assert(!"decoder is corrupt");
601 }
602 }
603
604Complete:
605 ret = bufsz - src;
606Exit:
607 if (dst != src)
608 memmove(buf + dst, buf + src, bufsz - src);
609 *_bufsz = dst;
610 return ret;
611}
612
613int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
614{
615 return decoder->_state == CHUNKED_IN_CHUNK_DATA;
616}
617
618#undef CHECK_EOF
619#undef EXPECT_CHAR
620#undef ADVANCE_TOKEN
diff --git a/plugins/picohttpparser.h b/plugins/picohttpparser.h
new file mode 100644
index 00000000..a8fad71d
--- /dev/null
+++ b/plugins/picohttpparser.h
@@ -0,0 +1,89 @@
1/*
2 * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
3 * Shigeo Mitsunari
4 *
5 * The software is licensed under either the MIT License (below) or the Perl
6 * license.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to
10 * deal in the Software without restriction, including without limitation the
11 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 * sell copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 * IN THE SOFTWARE.
25 */
26
27#ifndef picohttpparser_h
28#define picohttpparser_h
29
30#include <sys/types.h>
31
32#ifdef _MSC_VER
33#define ssize_t intptr_t
34#endif
35
36/* $Id: 67fd3ee74103ada60258d8a16e868f483abcca87 $ */
37
38#ifdef __cplusplus
39extern "C" {
40#endif
41
42/* contains name and value of a header (name == NULL if is a continuing line
43 * of a multiline header */
44struct phr_header {
45 const char *name;
46 size_t name_len;
47 const char *value;
48 size_t value_len;
49};
50
51/* returns number of bytes consumed if successful, -2 if request is partial,
52 * -1 if failed */
53int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
54 int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
55
56/* ditto */
57int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
58 struct phr_header *headers, size_t *num_headers, size_t last_len);
59
60/* ditto */
61int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
62
63/* should be zero-filled before start */
64struct phr_chunked_decoder {
65 size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
66 char consume_trailer; /* if trailing headers should be consumed */
67 char _hex_count;
68 char _state;
69};
70
71/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
72 * encoding headers. When the function returns without an error, bufsz is
73 * updated to the length of the decoded data available. Applications should
74 * repeatedly call the function while it returns -2 (incomplete) every time
75 * supplying newly arrived data. If the end of the chunked-encoded data is
76 * found, the function returns a non-negative number indicating the number of
77 * octets left undecoded at the tail of the supplied buffer. Returns -1 on
78 * error.
79 */
80ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
81
82/* returns if the chunked decoder is in middle of chunked data */
83int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
84
85#ifdef __cplusplus
86}
87#endif
88
89#endif