| #include <stdlib.h> |
| #include <stddef.h> |
| #include <ctype.h> |
| |
| #include "internal.h" |
| #include "evhtp/parser.h" |
| #include "evhtp/config.h" |
| #include "log.h" |
| |
| #if '\n' != '\x0a' || 'A' != 65 |
| #error "You have somehow found a non-ASCII host. We can't build here." |
| #endif |
| |
| #define PARSER_STACK_MAX 8192 |
| #define LF (unsigned char)10 |
| #define CR (unsigned char)13 |
| #define CRLF "\x0d\x0a" |
| |
| enum eval_hdr_val { |
| eval_hdr_val_none = 0, |
| eval_hdr_val_connection, |
| eval_hdr_val_proxy_connection, |
| eval_hdr_val_content_length, |
| eval_hdr_val_transfer_encoding, |
| eval_hdr_val_hostname, |
| eval_hdr_val_content_type |
| }; |
| |
| enum parser_flags { |
| parser_flag_chunked = (1 << 0), |
| parser_flag_connection_keep_alive = (1 << 1), |
| parser_flag_connection_close = (1 << 2), |
| parser_flag_trailing = (1 << 3), |
| }; |
| |
| enum parser_state { |
| s_start = 0, |
| s_method, |
| s_spaces_before_uri, |
| s_schema, |
| s_schema_slash, |
| s_schema_slash_slash, |
| s_host, |
| s_host_ipv6, |
| s_host_done, |
| s_port, |
| s_after_slash_in_uri, |
| s_check_uri, |
| s_uri, |
| s_http_09, |
| s_http_H, |
| s_http_HT, |
| s_http_HTT, |
| s_http_HTTP, |
| s_first_major_digit, |
| s_major_digit, |
| s_first_minor_digit, |
| s_minor_digit, |
| s_spaces_after_digit, |
| s_almost_done, |
| s_done, |
| s_hdrline_start, |
| s_hdrline_hdr_almost_done, |
| s_hdrline_hdr_done, |
| s_hdrline_hdr_key, |
| s_hdrline_hdr_space_before_val, |
| s_hdrline_hdr_val, |
| s_hdrline_almost_done, |
| s_hdrline_done, |
| s_body_read, |
| s_chunk_size_start, |
| s_chunk_size, |
| s_chunk_size_almost_done, |
| s_chunk_data, |
| s_chunk_data_almost_done, |
| s_chunk_data_done, |
| s_status, |
| s_space_after_status, |
| s_status_text |
| }; |
| |
| typedef enum eval_hdr_val eval_hdr_val; |
| typedef enum parser_flags parser_flags; |
| typedef enum parser_state parser_state; |
| |
| |
| struct htparser { |
| htpparse_error error; |
| parser_state state; |
| parser_flags flags; |
| eval_hdr_val heval; |
| |
| htp_type type; |
| htp_scheme scheme; |
| htp_method method; |
| |
| unsigned char multipart; |
| unsigned char major; |
| unsigned char minor; |
| uint64_t content_len; /* this gets decremented as data passes through */ |
| uint64_t orig_content_len; /* this contains the original length of the body */ |
| uint64_t bytes_read; |
| uint64_t total_bytes_read; |
| unsigned int status; /* only for responses */ |
| unsigned int status_count; /* only for responses */ |
| |
| char * scheme_offset; |
| char * host_offset; |
| char * port_offset; |
| char * path_offset; |
| char * args_offset; |
| |
| void * userdata; |
| |
| size_t buf_idx; |
| /* Must be last since htparser_init memsets up to the offset of this buffer */ |
| char buf[PARSER_STACK_MAX]; |
| }; |
| |
| #ifdef EVHTP_DEBUG |
| static void |
| log_htparser__s_(struct htparser * p) |
| { |
| log_debug( |
| "struct htparser {\n" |
| " htpparse_error = %d\n" |
| " parser_state = %d\n" |
| " parser_flags = %d\n" |
| " eval_hdr_val = %d\n" |
| " htp_type = %d\n" |
| " htp_scheme = %d\n" |
| " htp_method = %d\n" |
| " multipart = %c\n" |
| " major = %c\n" |
| " minor = %c\n" |
| " content_len = %zu\n" |
| " orig_clen = %zu\n" |
| " bytes_read = %zu\n" |
| " total_read = %zu\n" |
| " status = %d\n" |
| " status_count = %d\n" |
| " scheme_offset = %s\n" |
| " host_offset = %s\n" |
| " port_offset = %s\n" |
| " path_offset = %s\n" |
| " args_offset = %s\n" |
| " userdata = %p\n" |
| " buf_idx = %zu\n" |
| " buf = %s\n" |
| "};", |
| p->error, |
| p->state, |
| p->flags, |
| p->heval, |
| p->type, |
| p->scheme, |
| p->method, |
| p->multipart, |
| p->major, |
| p->minor, |
| p->content_len, |
| p->orig_content_len, |
| p->bytes_read, |
| p->total_bytes_read, |
| p->status, |
| p->status_count, |
| p->scheme_offset, |
| p->host_offset, |
| p->port_offset, |
| p->path_offset, |
| p->args_offset, |
| p->userdata, |
| p->buf_idx, |
| p->buf); |
| } /* log_htparser__s_ */ |
| |
| #else |
| #define log_htparser__s_(p) |
| #endif |
| |
| static uint32_t usual[] = { |
| 0xffffdbfe, |
| 0x7fff37d6, |
| 0xffffffff, |
| 0xffffffff, |
| 0xffffffff, |
| 0xffffffff, |
| 0xffffffff, |
| 0xffffffff |
| }; |
| |
| static int8_t unhex[256] = { |
| -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, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, |
| -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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 |
| }; |
| |
| static const char * errstr_map[] = { |
| "htparse_error_none", |
| "htparse_error_too_big", |
| "htparse_error_invalid_method", |
| "htparse_error_invalid_requestline", |
| "htparse_error_invalid_schema", |
| "htparse_error_invalid_protocol", |
| "htparse_error_invalid_version", |
| "htparse_error_invalid_header", |
| "htparse_error_invalid_chunk_size", |
| "htparse_error_invalid_chunk", |
| "htparse_error_invalid_state", |
| "htparse_error_user", |
| "htparse_error_unknown" |
| }; |
| |
| static const char * method_strmap[] = { |
| "GET", |
| "HEAD", |
| "POST", |
| "PUT", |
| "DELETE", |
| "MKCOL", |
| "COPY", |
| "MOVE", |
| "OPTIONS", |
| "PROPFIND", |
| "PROPATCH", |
| "LOCK", |
| "UNLOCK", |
| "TRACE", |
| "CONNECT", |
| "PATCH", |
| }; |
| |
| #define _MIN_READ(a, b) ((a) < (b) ? (a) : (b)) |
| |
| #ifndef HOST_BIG_ENDIAN |
| /* Little-endian cmp macros */ |
| #define _str3_cmp(m, c0, c1, c2, c3) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) |
| |
| #define _str3Ocmp(m, c0, c1, c2, c3) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) |
| |
| #define _str4cmp(m, c0, c1, c2, c3) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) |
| |
| #define _str5cmp(m, c0, c1, c2, c3, c4) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ |
| && m[4] == c4 |
| |
| #define _str6cmp(m, c0, c1, c2, c3, c4, c5) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ |
| && (((uint32_t *)m)[1] & 0xffff) == ((c5 << 8) | c4) |
| |
| #define _str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ |
| && ((uint32_t *)m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) |
| |
| #define _str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ |
| && ((uint32_t *)m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) |
| |
| #define _str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ |
| *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \ |
| && ((uint32_t *)m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) \ |
| && m[8] == c8 |
| #else |
| /* Big endian cmp macros */ |
| #define _str3_cmp(m, c0, c1, c2, c3) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 |
| |
| #define _str3Ocmp(m, c0, c1, c2, c3) \ |
| m[0] == c0 && m[2] == c2 && m[3] == c3 |
| |
| #define _str4cmp(m, c0, c1, c2, c3) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 |
| |
| #define _str5cmp(m, c0, c1, c2, c3, c4) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4 |
| |
| #define _str6cmp(m, c0, c1, c2, c3, c4, c5) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \ |
| && m[4] == c4 && m[5] == c5 |
| |
| #define _str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \ |
| && m[4] == c4 && m[5] == c5 && m[6] == c6 |
| |
| #define _str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \ |
| && m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7 |
| |
| #define _str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ |
| m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \ |
| && m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7 && m[8] == c8 |
| |
| #endif |
| |
| #define __HTPARSE_GENHOOK(__n) \ |
| static inline int hook_ ## __n ## _run(htparser * p, htparse_hooks * hooks) \ |
| { \ |
| log_debug("enter"); \ |
| if (hooks && (hooks)->__n) \ |
| { \ |
| return (hooks)->__n(p); \ |
| } \ |
| \ |
| return 0; \ |
| } |
| |
| #define __HTPARSE_GENDHOOK(__n) \ |
| static inline int hook_ ## __n ## _run(htparser * p, \ |
| htparse_hooks * hooks, \ |
| const char * s, size_t l) \ |
| { \ |
| log_debug("enter"); \ |
| if (hooks && (hooks)->__n) \ |
| { \ |
| return (hooks)->__n(p, s, l); \ |
| } \ |
| \ |
| return 0; \ |
| } |
| |
| __HTPARSE_GENHOOK(on_msg_begin) |
| __HTPARSE_GENHOOK(on_hdrs_begin) |
| __HTPARSE_GENHOOK(on_hdrs_complete) |
| __HTPARSE_GENHOOK(on_new_chunk) |
| __HTPARSE_GENHOOK(on_chunk_complete) |
| __HTPARSE_GENHOOK(on_chunks_complete) |
| __HTPARSE_GENHOOK(on_msg_complete) |
| |
| __HTPARSE_GENDHOOK(method) |
| __HTPARSE_GENDHOOK(scheme) |
| __HTPARSE_GENDHOOK(host) |
| __HTPARSE_GENDHOOK(port) |
| __HTPARSE_GENDHOOK(path) |
| __HTPARSE_GENDHOOK(args) |
| __HTPARSE_GENDHOOK(uri) |
| __HTPARSE_GENDHOOK(hdr_key) |
| __HTPARSE_GENDHOOK(hdr_val) |
| __HTPARSE_GENDHOOK(body) |
| __HTPARSE_GENDHOOK(hostname) |
| |
| |
| static inline uint64_t |
| str_to_uint64(char * str, size_t n, int * err) |
| { |
| uint64_t value; |
| |
| /* Trim whitespace after value. */ |
| while (n && isblank(str[n - 1])) |
| { |
| n--; |
| } |
| |
| if (n > 20) |
| { |
| /* 18446744073709551615 is 20 bytes */ |
| *err = 1; |
| return 0; |
| } |
| |
| for (value = 0; n--; str++) |
| { |
| uint64_t check; |
| |
| if (*str < '0' || *str > '9') |
| { |
| *err = 1; |
| return 0; |
| } |
| |
| check = value * 10 + (*str - '0'); |
| |
| if ((value && check <= value)) |
| { |
| *err = 1; |
| return 0; |
| } |
| |
| value = check; |
| } |
| |
| return value; |
| } |
| |
| static inline ssize_t |
| _str_to_ssize_t(char * str, size_t n) |
| { |
| ssize_t value; |
| |
| if (n == 0) |
| { |
| return -1; |
| } |
| |
| for (value = 0; n--; str++) |
| { |
| if (*str < '0' || *str > '9') |
| { |
| return -1; |
| } |
| |
| value = value * 10 + (*str - '0'); |
| |
| #if 0 |
| if (value > INTMAX_MAX) |
| { |
| return -1; |
| } |
| #endif |
| } |
| |
| return value; |
| } |
| |
| htpparse_error |
| htparser_get_error(htparser * p) |
| { |
| return p->error; |
| } |
| |
| const char * |
| htparser_get_strerror(htparser * p) |
| { |
| htpparse_error e = htparser_get_error(p); |
| |
| if (e > (htparse_error_generic + 1)) |
| { |
| return "htparse_no_such_error"; |
| } |
| |
| return errstr_map[e]; |
| } |
| |
| unsigned int |
| htparser_get_status(htparser * p) |
| { |
| return p->status; |
| } |
| |
| int |
| htparser_should_keep_alive(htparser * p) |
| { |
| if (p->major > 0 && p->minor > 0) |
| { |
| if (p->flags & parser_flag_connection_close) |
| { |
| return 0; |
| } else { |
| return 1; |
| } |
| } else { |
| if (p->flags & parser_flag_connection_keep_alive) |
| { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| htp_scheme |
| htparser_get_scheme(htparser * p) |
| { |
| return p->scheme; |
| } |
| |
| htp_method |
| htparser_get_method(htparser * p) |
| { |
| return p->method; |
| } |
| |
| const char * |
| htparser_get_methodstr_m(htp_method meth) |
| { |
| if (meth >= htp_method_UNKNOWN) |
| { |
| return NULL; |
| } |
| |
| return method_strmap[meth]; |
| } |
| |
| const char * |
| htparser_get_methodstr(htparser * p) |
| { |
| return htparser_get_methodstr_m(p->method); |
| } |
| |
| void |
| htparser_set_major(htparser * p, unsigned char major) |
| { |
| p->major = major; |
| } |
| |
| void |
| htparser_set_minor(htparser * p, unsigned char minor) |
| { |
| p->minor = minor; |
| } |
| |
| unsigned char |
| htparser_get_major(htparser * p) |
| { |
| return p->major; |
| } |
| |
| unsigned char |
| htparser_get_minor(htparser * p) |
| { |
| return p->minor; |
| } |
| |
| unsigned char |
| htparser_get_multipart(htparser * p) |
| { |
| return p->multipart; |
| } |
| |
| void * |
| htparser_get_userdata(htparser * p) |
| { |
| return p->userdata; |
| } |
| |
| void |
| htparser_set_userdata(htparser * p, void * ud) |
| { |
| p->userdata = ud; |
| } |
| |
| uint64_t |
| htparser_get_content_pending(htparser * p) |
| { |
| return p->content_len; |
| } |
| |
| uint64_t |
| htparser_get_content_length(htparser * p) |
| { |
| return p->orig_content_len; |
| } |
| |
| uint64_t |
| htparser_get_bytes_read(htparser * p) |
| { |
| return p->bytes_read; |
| } |
| |
| uint64_t |
| htparser_get_total_bytes_read(htparser * p) |
| { |
| return p->total_bytes_read; |
| } |
| |
| void |
| htparser_init(htparser * p, htp_type type) |
| { |
| /* Do not memset entire string buffer. */ |
| memset(p, 0, offsetof(htparser, buf)); |
| p->buf[0] = '\0'; |
| p->state = s_start; |
| p->error = htparse_error_none; |
| p->method = htp_method_UNKNOWN; |
| p->type = type; |
| } |
| |
| htparser * |
| htparser_new(void) |
| { |
| return malloc(sizeof(htparser)); |
| } |
| |
| static int |
| is_host_char(unsigned char ch) |
| { |
| char c = (unsigned char)(ch | 0x20); |
| |
| if (c >= 'a' && c <= 'z') |
| { |
| return 1; |
| } |
| |
| if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') |
| { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static htp_method |
| get_method(const char * m, const size_t sz) |
| { |
| switch (sz) { |
| case 3: |
| if (_str3_cmp(m, 'G', 'E', 'T', '\0')) |
| { |
| return htp_method_GET; |
| } |
| |
| if (_str3_cmp(m, 'P', 'U', 'T', '\0')) |
| { |
| return htp_method_PUT; |
| } |
| |
| break; |
| case 4: |
| if (m[1] == 'O') |
| { |
| if (_str3Ocmp(m, 'P', 'O', 'S', 'T')) |
| { |
| return htp_method_POST; |
| } |
| |
| if (_str3Ocmp(m, 'C', 'O', 'P', 'Y')) |
| { |
| return htp_method_COPY; |
| } |
| |
| if (_str3Ocmp(m, 'M', 'O', 'V', 'E')) |
| { |
| return htp_method_MOVE; |
| } |
| |
| if (_str3Ocmp(m, 'L', 'O', 'C', 'K')) |
| { |
| return htp_method_LOCK; |
| } |
| } else { |
| if (_str4cmp(m, 'H', 'E', 'A', 'D')) |
| { |
| return htp_method_HEAD; |
| } |
| } |
| |
| break; |
| case 5: |
| if (_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) |
| { |
| return htp_method_MKCOL; |
| } |
| |
| if (_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) |
| { |
| return htp_method_TRACE; |
| } |
| |
| if (_str5cmp(m, 'P', 'A', 'T', 'C', 'H')) |
| { |
| return htp_method_PATCH; |
| } |
| |
| break; |
| case 6: |
| if (_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) |
| { |
| return htp_method_DELETE; |
| } |
| |
| if (_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) |
| { |
| return htp_method_UNLOCK; |
| } |
| |
| break; |
| case 7: |
| if (_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', '\0')) |
| { |
| return htp_method_OPTIONS; |
| } |
| |
| if (_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', '\0')) |
| { |
| return htp_method_CONNECT; |
| } |
| |
| break; |
| case 8: |
| if (_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D')) |
| { |
| return htp_method_PROPFIND; |
| } |
| |
| break; |
| |
| case 9: |
| if (_str9cmp(m, 'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H')) |
| { |
| return htp_method_PROPPATCH; |
| } |
| |
| break; |
| } /* switch */ |
| |
| return htp_method_UNKNOWN; |
| } /* get_method */ |
| |
| size_t |
| htparser_run(htparser * p, htparse_hooks * hooks, const char * data, size_t len) |
| { |
| unsigned char ch; |
| char c; |
| size_t i; |
| |
| log_debug("enter"); |
| log_debug("p == %p", p); |
| |
| p->error = htparse_error_none; |
| p->bytes_read = 0; |
| |
| for (i = 0; i < len; i++) |
| { |
| int res; |
| int err; |
| |
| ch = data[i]; |
| |
| log_debug("[%p] data[%zu] = %c (%x)", p, i, isprint(ch) ? ch : ' ', ch); |
| |
| if (p->buf_idx >= PARSER_STACK_MAX) |
| { |
| p->error = htparse_error_too_big; |
| return i + 1; |
| } |
| |
| p->total_bytes_read += 1; |
| p->bytes_read += 1; |
| |
| switch (p->state) { |
| case s_start: |
| log_debug("[%p] s_start", p); |
| |
| if (ch == CR || ch == LF) |
| { |
| break; |
| } |
| |
| if ((ch < 'A' || ch > 'Z') && ch != '_') |
| { |
| p->error = htparse_error_inval_reqline; |
| |
| log_debug("s_start invalid fist char '%c'", ch); |
| log_htparser__s_(p); |
| |
| return i + 1; |
| } |
| |
| |
| p->flags = 0; |
| p->error = htparse_error_none; |
| p->method = htp_method_UNKNOWN; |
| p->multipart = 0; |
| p->major = 0; |
| p->minor = 0; |
| p->content_len = 0; |
| p->orig_content_len = 0; |
| p->status = 0; |
| p->status_count = 0; |
| p->scheme_offset = NULL; |
| p->host_offset = NULL; |
| p->port_offset = NULL; |
| p->path_offset = NULL; |
| p->args_offset = NULL; |
| |
| |
| res = hook_on_msg_begin_run(p, hooks); |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| if (evhtp_likely(p->type == htp_type_request)) |
| { |
| p->state = s_method; |
| } else if (p->type == htp_type_response && ch == 'H') |
| { |
| p->state = s_http_H; |
| } else { |
| log_debug("not type of request or response?"); |
| log_htparser__s_(p); |
| |
| p->error = htparse_error_inval_reqline; |
| return i + 1; |
| } |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_method: |
| log_debug("[%p] s_method", p); |
| |
| do { |
| if (ch == ' ') |
| { |
| p->method = get_method(p->buf, p->buf_idx); |
| res = hook_method_run(p, hooks, p->buf, p->buf_idx); |
| |
| p->buf_idx = 0; |
| p->state = s_spaces_before_uri; |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| } else { |
| if ((ch < 'A' || ch > 'Z') && ch != '_') |
| { |
| p->error = htparse_error_inval_method; |
| return i + 1; |
| } |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| } |
| |
| ch = data[++i]; |
| } while (i < len); |
| |
| break; |
| case s_spaces_before_uri: |
| log_debug("[%p] s_spaces_before_uri", p); |
| |
| /* CONNECT is special - RFC 2817 section 5.2: |
| * The Request-URI portion of the Request-Line is |
| * always an 'authority' as defined by URI Generic |
| * Syntax [2], which is to say the host name and port |
| * number destination of the requested connection |
| * separated by a colon |
| */ |
| if (p->method == htp_method_CONNECT) |
| { |
| switch (ch) { |
| case ' ': |
| break; |
| case '[': |
| /* Literal IPv6 address start. */ |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->host_offset = &p->buf[p->buf_idx]; |
| |
| p->state = s_host_ipv6; |
| break; |
| default: |
| if (!is_host_char(ch)) |
| { |
| p->error = htparse_error_inval_reqline; |
| log_htparser__s_(p); |
| |
| return i + 1; |
| } |
| p->host_offset = &p->buf[p->buf_idx]; |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_host; |
| break; |
| } /* switch */ |
| |
| break; |
| } |
| |
| switch (ch) { |
| case ' ': |
| break; |
| case '/': |
| p->path_offset = &p->buf[p->buf_idx]; |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_after_slash_in_uri; |
| break; |
| default: |
| c = (unsigned char)(ch | 0x20); |
| |
| if (c >= 'a' && c <= 'z') |
| { |
| p->scheme_offset = &p->buf[p->buf_idx]; |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_schema; |
| break; |
| } |
| |
| p->error = htparse_error_inval_reqline; |
| log_htparser__s_(p); |
| |
| return i + 1; |
| } /* switch */ |
| |
| break; |
| case s_schema: |
| log_debug("[%p] s_schema", p); |
| |
| c = (unsigned char)(ch | 0x20); |
| |
| if (c >= 'a' && c <= 'z') |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } |
| |
| switch (ch) { |
| case ':': |
| p->scheme = htp_scheme_unknown; |
| |
| switch (p->buf_idx) { |
| case 3: |
| if (_str3_cmp(p->scheme_offset, 'f', 't', 'p', '\0')) |
| { |
| p->scheme = htp_scheme_ftp; |
| break; |
| } |
| |
| if (_str3_cmp(p->scheme_offset, 'n', 'f', 's', '\0')) |
| { |
| p->scheme = htp_scheme_nfs; |
| break; |
| } |
| |
| break; |
| case 4: |
| if (_str4cmp(p->scheme_offset, 'h', 't', 't', 'p')) |
| { |
| p->scheme = htp_scheme_http; |
| break; |
| } |
| break; |
| case 5: |
| if (_str5cmp(p->scheme_offset, 'h', 't', 't', 'p', 's')) |
| { |
| p->scheme = htp_scheme_https; |
| break; |
| } |
| break; |
| } /* switch */ |
| |
| res = hook_scheme_run(p, hooks, |
| p->scheme_offset, |
| (&p->buf[p->buf_idx] - p->scheme_offset)); |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_schema_slash; |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| default: |
| p->error = htparse_error_inval_schema; |
| return i + 1; |
| } /* switch */ |
| |
| break; |
| case s_schema_slash: |
| log_debug("[%p] s_schema_slash", p); |
| |
| switch (ch) { |
| case '/': |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_schema_slash_slash; |
| break; |
| default: |
| p->error = htparse_error_inval_schema; |
| return i + 1; |
| } |
| break; |
| case s_schema_slash_slash: |
| log_debug("[%p] s_schema_slash_slash", p); |
| |
| switch (ch) { |
| case '/': |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->host_offset = &p->buf[p->buf_idx]; |
| |
| p->state = s_host; |
| break; |
| default: |
| p->error = htparse_error_inval_schema; |
| return i + 1; |
| } |
| break; |
| case s_host: |
| if (ch == '[') |
| { |
| /* Literal IPv6 address start. */ |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->host_offset = &p->buf[p->buf_idx]; |
| |
| p->state = s_host_ipv6; |
| break; |
| } |
| |
| if (is_host_char(ch)) |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } |
| |
| res = hook_host_run(p, hooks, |
| p->host_offset, |
| (&p->buf[p->buf_idx] - p->host_offset)); |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| /* successfully parsed a NON-IPV6 hostname, knowing this, the |
| * current character in 'ch' is actually the next state, so we |
| * we fall through to avoid another loop. |
| */ |
| case s_host_done: |
| res = 0; |
| |
| switch (ch) { |
| case ':': |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->port_offset = &p->buf[p->buf_idx]; |
| p->state = s_port; |
| break; |
| case ' ': |
| /* this technically should never happen, but we should |
| * check anyway |
| */ |
| if (i == 0) |
| { |
| p->error = htparse_error_inval_state; |
| return i + 1; |
| } |
| |
| i--; |
| ch = '/'; |
| /* to accept requests like <method> <proto>://<host> <ver> |
| * we fallthrough to the next case. |
| */ |
| case '/': |
| p->path_offset = &p->buf[p->buf_idx]; |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_after_slash_in_uri; |
| break; |
| default: |
| p->error = htparse_error_inval_schema; |
| return i + 1; |
| } /* switch */ |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case s_host_ipv6: |
| c = (unsigned char)(ch | 0x20); |
| |
| if ((c >= 'a' && c <= 'f') || |
| (ch >= '0' && ch <= '9') || ch == ':' || ch == '.') |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } |
| |
| switch (ch) { |
| case ']': |
| res = hook_host_run(p, hooks, p->host_offset, |
| (&p->buf[p->buf_idx] - p->host_offset)); |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_host_done; |
| break; |
| default: |
| p->error = htparse_error_inval_schema; |
| return i + 1; |
| } |
| break; |
| case s_port: |
| if (ch >= '0' && ch <= '9') |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } |
| |
| res = hook_port_run(p, hooks, p->port_offset, |
| (&p->buf[p->buf_idx] - p->port_offset)); |
| |
| switch (ch) { |
| case ' ': |
| /* this technically should never happen, but we should |
| * check anyway |
| */ |
| if (i == 0) |
| { |
| p->error = htparse_error_inval_state; |
| return i + 1; |
| } |
| |
| i--; |
| ch = '/'; |
| /* to accept requests like <method> <proto>://<host> <ver> |
| * we fallthrough to the next case. |
| */ |
| case '/': |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->path_offset = &p->buf[p->buf_idx - 1]; |
| |
| p->state = s_after_slash_in_uri; |
| break; |
| default: |
| p->error = htparse_error_inval_reqline; |
| log_debug("[s_port] inval_reqline"); |
| log_htparser__s_(p); |
| |
| return i + 1; |
| } /* switch */ |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case s_after_slash_in_uri: |
| log_debug("[%p] s_after_slash_in_uri", p); |
| |
| res = 0; |
| |
| if (usual[ch >> 5] & (1 << (ch & 0x1f))) |
| { |
| if (evhtp_likely((p->buf_idx + 1) < PARSER_STACK_MAX)) |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_check_uri; |
| } |
| break; |
| } |
| |
| switch (ch) { |
| case ' ': |
| { |
| int r1 = hook_path_run(p, hooks, p->path_offset, |
| (&p->buf[p->buf_idx] - p->path_offset)); |
| int r2 = hook_uri_run(p, hooks, p->buf, p->buf_idx); |
| |
| p->state = s_http_09; |
| p->buf_idx = 0; |
| |
| if (r1 || r2) |
| { |
| res = 1; |
| } |
| } |
| |
| break; |
| case CR: |
| p->minor = 9; |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->minor = 9; |
| p->state = s_hdrline_start; |
| break; |
| case '.': |
| case '%': |
| case '/': |
| case '#': |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_uri; |
| break; |
| case '?': |
| res = hook_path_run(p, hooks, p->path_offset, |
| (&p->buf[p->buf_idx] - p->path_offset)); |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->args_offset = &p->buf[p->buf_idx]; |
| p->state = s_uri; |
| |
| break; |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_check_uri; |
| break; |
| } /* switch */ |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_check_uri: |
| |
| res = 0; |
| |
| do { |
| log_debug("[%p] s_check_uri", p); |
| if (usual[ch >> 5] & (1 << (ch & 0x1f))) |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| } else { |
| break; |
| } |
| |
| ch = data[++i]; |
| } while (i < len); |
| |
| switch (ch) { |
| case ' ': |
| { |
| int r1 = 0; |
| int r2 = 0; |
| |
| if (p->args_offset) |
| { |
| r1 = hook_args_run(p, hooks, p->args_offset, |
| (&p->buf[p->buf_idx] - p->args_offset)); |
| } else { |
| r1 = hook_path_run(p, hooks, p->path_offset, |
| (&p->buf[p->buf_idx] - p->path_offset)); |
| } |
| |
| r2 = hook_uri_run(p, hooks, p->buf, p->buf_idx); |
| p->buf_idx = 0; |
| p->state = s_http_09; |
| |
| if (r1 || r2) |
| { |
| res = 1; |
| } |
| } |
| break; |
| case '/': |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_after_slash_in_uri; |
| break; |
| case CR: |
| p->minor = 9; |
| p->buf_idx = 0; |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->minor = 9; |
| p->buf_idx = 0; |
| |
| p->state = s_hdrline_start; |
| break; |
| case '?': |
| res = hook_path_run(p, hooks, |
| p->path_offset, |
| (&p->buf[p->buf_idx] - p->path_offset)); |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->args_offset = &p->buf[p->buf_idx]; |
| p->state = s_uri; |
| break; |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_uri; |
| break; |
| } /* switch */ |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_uri: |
| log_debug("[%p] s_uri", p); |
| |
| res = 0; |
| |
| do { |
| if (usual[ch >> 5] & (1 << (ch & 0x1f))) |
| { |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| } else { |
| break; |
| } |
| |
| ch = data[++i]; |
| } while (i < len); |
| |
| switch (ch) { |
| case ' ': |
| { |
| int r1 = 0; |
| int r2 = 0; |
| |
| if (p->args_offset) |
| { |
| r1 = hook_args_run(p, hooks, p->args_offset, |
| (&p->buf[p->buf_idx] - p->args_offset)); |
| } else { |
| r1 = hook_path_run(p, hooks, p->path_offset, |
| (&p->buf[p->buf_idx] - p->path_offset)); |
| } |
| |
| p->buf_idx = 0; |
| p->state = s_http_09; |
| |
| if (r1 || r2) |
| { |
| res = 1; |
| } |
| } |
| break; |
| case CR: |
| p->minor = 9; |
| p->buf_idx = 0; |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->minor = 9; |
| p->buf_idx = 0; |
| p->state = s_hdrline_start; |
| break; |
| case '?': |
| /* RFC 3986 section 3.4: |
| * The query component is indicated by the |
| * first question mark ("?") character and |
| * terminated by a number sign ("#") character |
| * or by the end of the URI. */ |
| if (!p->args_offset) |
| { |
| res = hook_path_run(p, hooks, p->path_offset, |
| (&p->buf[p->buf_idx] - p->path_offset)); |
| |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->args_offset = &p->buf[p->buf_idx]; |
| break; |
| } |
| /* Fall through. */ |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } /* switch */ |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_http_09: |
| log_debug("[%p] s_http_09", p); |
| |
| switch (ch) { |
| case ' ': |
| break; |
| case CR: |
| p->minor = 9; |
| p->buf_idx = 0; |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->minor = 9; |
| p->buf_idx = 0; |
| p->state = s_hdrline_start; |
| break; |
| case 'H': |
| p->buf_idx = 0; |
| p->state = s_http_H; |
| break; |
| default: |
| p->error = htparse_error_inval_proto; |
| return i + 1; |
| } /* switch */ |
| |
| break; |
| case s_http_H: |
| log_debug("[%p] s_http_H", p); |
| |
| switch (ch) { |
| case 'T': |
| p->state = s_http_HT; |
| break; |
| default: |
| p->error = htparse_error_inval_proto; |
| return i + 1; |
| } |
| break; |
| case s_http_HT: |
| switch (ch) { |
| case 'T': |
| p->state = s_http_HTT; |
| break; |
| default: |
| p->error = htparse_error_inval_proto; |
| return i + 1; |
| } |
| break; |
| case s_http_HTT: |
| switch (ch) { |
| case 'P': |
| p->state = s_http_HTTP; |
| break; |
| default: |
| p->error = htparse_error_inval_proto; |
| return i + 1; |
| } |
| break; |
| case s_http_HTTP: |
| switch (ch) { |
| case '/': |
| p->state = s_first_major_digit; |
| break; |
| default: |
| p->error = htparse_error_inval_proto; |
| return i + 1; |
| } |
| break; |
| case s_first_major_digit: |
| if (ch < '1' || ch > '9') |
| { |
| p->error = htparse_error_inval_ver; |
| return i + 1; |
| } |
| |
| p->major = ch - '0'; |
| p->state = s_major_digit; |
| break; |
| case s_major_digit: |
| if (ch == '.') |
| { |
| p->state = s_first_minor_digit; |
| break; |
| } |
| |
| if (ch < '0' || ch > '9') |
| { |
| p->error = htparse_error_inval_ver; |
| return i + 1; |
| } |
| |
| p->major = p->major * 10 + ch - '0'; |
| break; |
| case s_first_minor_digit: |
| if (ch < '0' || ch > '9') |
| { |
| p->error = htparse_error_inval_ver; |
| return i + 1; |
| } |
| |
| p->minor = ch - '0'; |
| p->state = s_minor_digit; |
| break; |
| case s_minor_digit: |
| switch (ch) { |
| case ' ': |
| if (evhtp_likely(p->type == htp_type_request)) |
| { |
| p->state = s_spaces_after_digit; |
| } else if (p->type == htp_type_response) |
| { |
| p->state = s_status; |
| } |
| |
| break; |
| case CR: |
| p->state = s_almost_done; |
| break; |
| case LF: |
| /* LF without a CR? error.... */ |
| p->error = htparse_error_inval_reqline; |
| log_debug("[s_minor_digit] LF without CR!"); |
| log_htparser__s_(p); |
| |
| return i + 1; |
| default: |
| if (ch < '0' || ch > '9') |
| { |
| p->error = htparse_error_inval_ver; |
| return i + 1; |
| } |
| |
| p->minor = p->minor * 10 + ch - '0'; |
| break; |
| } /* switch */ |
| break; |
| case s_status: |
| /* http response status code */ |
| if (ch == ' ') |
| { |
| if (p->status) |
| { |
| p->state = s_status_text; |
| } |
| break; |
| } |
| |
| if (ch < '0' || ch > '9') |
| { |
| p->error = htparse_error_status; |
| return i + 1; |
| } |
| |
| p->status = p->status * 10 + ch - '0'; |
| |
| if (++p->status_count == 3) |
| { |
| p->state = s_space_after_status; |
| } |
| |
| break; |
| case s_space_after_status: |
| switch (ch) { |
| case ' ': |
| p->state = s_status_text; |
| break; |
| case CR: |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->state = s_hdrline_start; |
| break; |
| default: |
| p->error = htparse_error_generic; |
| return i + 1; |
| } |
| break; |
| case s_status_text: |
| switch (ch) { |
| case CR: |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->state = s_hdrline_start; |
| break; |
| default: |
| break; |
| } |
| break; |
| case s_spaces_after_digit: |
| switch (ch) { |
| case ' ': |
| break; |
| case CR: |
| p->state = s_almost_done; |
| break; |
| case LF: |
| p->state = s_hdrline_start; |
| break; |
| default: |
| p->error = htparse_error_inval_ver; |
| return i + 1; |
| } |
| break; |
| |
| case s_almost_done: |
| switch (ch) { |
| case LF: |
| if (p->type == htp_type_response && p->status >= 100 && p->status < 200) |
| { |
| res = hook_on_hdrs_begin_run(p, hooks); |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| p->status = 0; |
| p->status_count = 0; |
| p->state = s_start; |
| break; |
| } |
| |
| p->state = s_done; |
| res = hook_on_hdrs_begin_run(p, hooks); |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| break; |
| default: |
| p->error = htparse_error_inval_reqline; |
| log_htparser__s_(p); |
| |
| return i + 1; |
| } /* switch */ |
| break; |
| case s_done: |
| switch (ch) { |
| case CR: |
| p->state = s_hdrline_almost_done; |
| break; |
| case LF: |
| return i + 1; |
| default: |
| goto hdrline_start; |
| } |
| break; |
| hdrline_start: |
| case s_hdrline_start: |
| log_debug("[%p] s_hdrline_start", p); |
| |
| p->buf_idx = 0; |
| |
| switch (ch) { |
| case CR: |
| p->state = s_hdrline_hdr_almost_done; |
| break; |
| case LF: |
| p->state = s_hdrline_hdr_done; |
| break; |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_hdrline_hdr_key; |
| break; |
| } |
| |
| break; |
| case s_hdrline_hdr_key: |
| log_debug("[%p] s_hdrline_hdr_key", p); |
| |
| do { |
| if (evhtp_unlikely(ch == ':')) |
| { |
| res = hook_hdr_key_run(p, hooks, p->buf, p->buf_idx); |
| |
| /* figure out if the value of this header is valueable */ |
| p->heval = eval_hdr_val_none; |
| |
| switch (p->buf_idx + 1) { |
| case 5: |
| if (!strcasecmp(p->buf, "host")) |
| { |
| p->heval = eval_hdr_val_hostname; |
| } |
| break; |
| case 11: |
| if (!strcasecmp(p->buf, "connection")) |
| { |
| p->heval = eval_hdr_val_connection; |
| } |
| break; |
| case 13: |
| if (!strcasecmp(p->buf, "content-type")) |
| { |
| p->heval = eval_hdr_val_content_type; |
| } |
| break; |
| case 15: |
| if (!strcasecmp(p->buf, "content-length")) |
| { |
| p->heval = eval_hdr_val_content_length; |
| } |
| break; |
| case 17: |
| if (!strcasecmp(p->buf, "proxy-connection")) |
| { |
| p->heval = eval_hdr_val_proxy_connection; |
| } |
| break; |
| case 18: |
| if (!strcasecmp(p->buf, "transfer-encoding")) |
| { |
| p->heval = eval_hdr_val_transfer_encoding; |
| } |
| break; |
| } /* switch */ |
| |
| p->buf_idx = 0; |
| p->state = s_hdrline_hdr_space_before_val; |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| } |
| |
| switch (ch) { |
| case CR: |
| p->state = s_hdrline_hdr_almost_done; |
| break; |
| case LF: |
| p->state = s_hdrline_hdr_done; |
| break; |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } |
| |
| if (p->state != s_hdrline_hdr_key) |
| { |
| break; |
| } |
| |
| ch = data[++i]; |
| } while (i < len); |
| |
| break; |
| |
| case s_hdrline_hdr_space_before_val: |
| log_debug("[%p] s_hdrline_hdr_space_before_val", p); |
| |
| switch (ch) { |
| case ' ': |
| break; |
| case CR: |
| /* |
| * we have an empty header value here, so we set the buf |
| * to empty, set the state to hdrline_hdr_val, and |
| * decrement the start byte counter. |
| */ |
| p->buf[p->buf_idx++] = ' '; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_hdrline_hdr_val; |
| |
| /* |
| * make sure the next pass comes back to this CR byte, |
| * so it matches in s_hdrline_hdr_val. |
| */ |
| i--; |
| break; |
| case LF: |
| /* never got a CR for an empty header, this is an |
| * invalid state. |
| */ |
| p->error = htparse_error_inval_hdr; |
| return i + 1; |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| p->state = s_hdrline_hdr_val; |
| break; |
| } /* switch */ |
| break; |
| case s_hdrline_hdr_val: |
| err = 0; |
| |
| do { |
| log_debug("[%p] s_hdrline_hdr_val", p); |
| if (ch == CR) |
| { |
| switch (p->heval) { |
| case eval_hdr_val_none: |
| break; |
| case eval_hdr_val_hostname: |
| if (hook_hostname_run(p, hooks, p->buf, p->buf_idx)) |
| { |
| p->state = s_hdrline_hdr_almost_done; |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case eval_hdr_val_content_length: |
| p->content_len = str_to_uint64(p->buf, p->buf_idx, &err); |
| p->orig_content_len = p->content_len; |
| |
| log_debug("[%p] s_hdrline_hdr_val content-lenth = %zu", p, p->content_len); |
| |
| if (err == 1) |
| { |
| p->error = htparse_error_too_big; |
| return i + 1; |
| } |
| |
| break; |
| case eval_hdr_val_connection: |
| switch (p->buf[0]) { |
| char A_case; |
| char C_case; |
| const char * S_buf; |
| |
| case 'K': |
| case 'k': |
| if (p->buf_idx != 10) |
| { |
| break; |
| } |
| |
| A_case = (p->buf[5] == 'A') ? 'A' : 'a'; |
| S_buf = (const char *)(p->buf + 1); |
| |
| if (_str9cmp(S_buf, |
| 'e', 'e', 'p', '-', A_case, 'l', 'i', 'v', 'e')) |
| { |
| p->flags |= parser_flag_connection_keep_alive; |
| } |
| break; |
| case 'c': |
| case 'C': |
| if (p->buf_idx != 5) |
| { |
| break; |
| } |
| |
| C_case = (p->buf[0] == 'C') ? 'C' : 'c'; |
| S_buf = (const char *)p->buf; |
| |
| if (_str5cmp(S_buf, C_case, 'l', 'o', 's', 'e')) |
| { |
| p->flags |= parser_flag_connection_close; |
| } |
| break; |
| } /* switch */ |
| break; |
| case eval_hdr_val_transfer_encoding: |
| if (p->buf_idx != 7) |
| { |
| break; |
| } |
| |
| switch (p->buf[0]) { |
| const char * S_buf; |
| |
| case 'c': |
| case 'C': |
| if (p->buf_idx != 7) |
| { |
| break; |
| } |
| |
| S_buf = (const char *)(p->buf + 1); |
| |
| if (_str6cmp(S_buf, 'h', 'u', 'n', 'k', 'e', 'd')) |
| { |
| p->flags |= parser_flag_chunked; |
| } |
| |
| break; |
| } |
| |
| break; |
| case eval_hdr_val_content_type: |
| if (p->buf_idx != 9) |
| { |
| break; |
| } |
| |
| switch (p->buf[0]) { |
| const char * S_buf; |
| |
| case 'm': |
| case 'M': |
| S_buf = (const char *)(p->buf + 1); |
| |
| if (_str8cmp(S_buf, 'u', 'l', 't', 'i', 'p', 'a', 'r', 't')) |
| { |
| p->multipart = 1; |
| } |
| |
| break; |
| } |
| |
| break; |
| case eval_hdr_val_proxy_connection: |
| default: |
| break; |
| } /* switch */ |
| |
| p->state = s_hdrline_hdr_almost_done; |
| |
| break; |
| } |
| |
| switch (ch) { |
| case LF: |
| /* LF before CR? invalid */ |
| p->error = htparse_error_inval_hdr; |
| return i + 1; |
| default: |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| break; |
| } /* switch */ |
| |
| if (p->state != s_hdrline_hdr_val) |
| { |
| break; |
| } |
| |
| ch = data[++i]; |
| } while (i < len); |
| |
| break; |
| case s_hdrline_hdr_almost_done: |
| log_debug("[%p] s_hdrline_hdr_almost_done", p); |
| |
| res = 0; |
| switch (ch) { |
| case LF: |
| if (p->flags & parser_flag_trailing) |
| { |
| res = hook_on_msg_complete_run(p, hooks); |
| p->state = s_start; |
| break; |
| } |
| |
| p->state = s_hdrline_hdr_done; |
| break; |
| default: |
| p->error = htparse_error_inval_hdr; |
| return i + 1; |
| } |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case s_hdrline_hdr_done: |
| log_debug("[%p] s_hdrline_hdr_done", p); |
| |
| switch (ch) { |
| case CR: |
| res = hook_hdr_val_run(p, hooks, p->buf, p->buf_idx); |
| p->state = s_hdrline_almost_done; |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case LF: |
| /* got LFLF? is this valid? */ |
| p->error = htparse_error_inval_hdr; |
| |
| return i + 1; |
| case '\t': |
| /* this is a multiline header value, we must go back to |
| * reading as a header value */ |
| p->state = s_hdrline_hdr_val; |
| break; |
| default: |
| res = hook_hdr_val_run(p, hooks, p->buf, p->buf_idx); |
| |
| p->buf_idx = 0; |
| p->buf[p->buf_idx++] = ch; |
| p->buf[p->buf_idx] = '\0'; |
| |
| p->state = s_hdrline_hdr_key; |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| } /* switch */ |
| break; |
| case s_hdrline_almost_done: |
| log_debug("[%p] s_hdrline_almost_done", p); |
| |
| switch (ch) { |
| case LF: |
| res = hook_on_hdrs_complete_run(p, hooks); |
| |
| if (res != 0) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| p->buf_idx = 0; |
| |
| if (p->flags & parser_flag_trailing) |
| { |
| res = hook_on_msg_complete_run(p, hooks); |
| p->state = s_start; |
| } else if (p->flags & parser_flag_chunked) |
| { |
| p->state = s_chunk_size_start; |
| } else if (p->content_len > 0) |
| { |
| p->state = s_body_read; |
| } else if (p->content_len == 0) |
| { |
| res = hook_on_msg_complete_run(p, hooks); |
| p->state = s_start; |
| } else { |
| p->state = s_hdrline_done; |
| } |
| |
| if (res != 0) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| break; |
| |
| default: |
| p->error = htparse_error_inval_hdr; |
| return i + 1; |
| } /* switch */ |
| |
| if (res != 0) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case s_hdrline_done: |
| log_debug("[%p] s_hdrline_done", p); |
| |
| res = 0; |
| |
| if (p->flags & parser_flag_trailing) |
| { |
| res = hook_on_msg_complete_run(p, hooks); |
| p->state = s_start; |
| } else if (p->flags & parser_flag_chunked) |
| { |
| p->state = s_chunk_size_start; |
| i--; |
| } else if (p->content_len > 0) |
| { |
| p->state = s_body_read; |
| i--; |
| } else if (p->content_len == 0) |
| { |
| res = hook_on_msg_complete_run(p, hooks); |
| p->state = s_start; |
| } |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| case s_chunk_size_start: |
| c = unhex[(unsigned char)ch]; |
| |
| if (c == -1) |
| { |
| p->error = htparse_error_inval_chunk_sz; |
| return i + 1; |
| } |
| |
| p->content_len = c; |
| p->state = s_chunk_size; |
| break; |
| case s_chunk_size: |
| if (ch == CR) |
| { |
| p->state = s_chunk_size_almost_done; |
| break; |
| } |
| |
| c = unhex[(unsigned char)ch]; |
| |
| if (c == -1) |
| { |
| p->error = htparse_error_inval_chunk_sz; |
| return i + 1; |
| } |
| |
| p->content_len *= 16; |
| p->content_len += c; |
| break; |
| |
| case s_chunk_size_almost_done: |
| if (ch != LF) |
| { |
| p->error = htparse_error_inval_chunk_sz; |
| return i + 1; |
| } |
| |
| p->orig_content_len = p->content_len; |
| |
| if (p->content_len == 0) |
| { |
| res = hook_on_chunks_complete_run(p, hooks); |
| |
| p->flags |= parser_flag_trailing; |
| p->state = s_hdrline_start; |
| } else { |
| res = hook_on_new_chunk_run(p, hooks); |
| |
| p->state = s_chunk_data; |
| } |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_chunk_data: |
| res = 0; |
| { |
| const char * pp = &data[i]; |
| const char * pe = (const char *)(data + len); |
| size_t to_read = _MIN_READ(pe - pp, p->content_len); |
| |
| if (to_read > 0) |
| { |
| res = hook_body_run(p, hooks, pp, to_read); |
| |
| i += to_read - 1; |
| } |
| |
| if (to_read == p->content_len) |
| { |
| p->state = s_chunk_data_almost_done; |
| } |
| |
| p->content_len -= to_read; |
| } |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_chunk_data_almost_done: |
| if (ch != CR) |
| { |
| p->error = htparse_error_inval_chunk; |
| return i + 1; |
| } |
| |
| p->state = s_chunk_data_done; |
| break; |
| |
| case s_chunk_data_done: |
| if (ch != LF) |
| { |
| p->error = htparse_error_inval_chunk; |
| return i + 1; |
| } |
| |
| p->orig_content_len = 0; |
| p->state = s_chunk_size_start; |
| |
| if (hook_on_chunk_complete_run(p, hooks)) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| |
| break; |
| |
| case s_body_read: |
| res = 0; |
| |
| { |
| const char * pp = &data[i]; |
| const char * pe = (const char *)(data + len); |
| size_t to_read = _MIN_READ(pe - pp, p->content_len); |
| |
| if (to_read > 0) |
| { |
| res = hook_body_run(p, hooks, pp, to_read); |
| |
| i += to_read - 1; |
| p->content_len -= to_read; |
| } |
| |
| if (p->content_len == 0) |
| { |
| res = hook_on_msg_complete_run(p, hooks); |
| p->state = s_start; |
| } |
| |
| if (res) |
| { |
| p->error = htparse_error_user; |
| return i + 1; |
| } |
| } |
| |
| break; |
| |
| default: |
| log_debug("[%p] This is a silly state....", p); |
| p->error = htparse_error_inval_state; |
| return i + 1; |
| } /* switch */ |
| |
| /* If we successfully completed a request/response we return |
| * to caller, and leave it up to him to call us again if |
| * parsing should continue. */ |
| if (p->state == s_start) |
| { |
| return i + 1; |
| } |
| } /* switch */ |
| |
| return i; |
| } /* htparser_run */ |