diff options
Diffstat (limited to 'src/or/proto_http.c')
-rw-r--r-- | src/or/proto_http.c | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/or/proto_http.c b/src/or/proto_http.c new file mode 100644 index 0000000000..2ba3f93ab6 --- /dev/null +++ b/src/or/proto_http.c @@ -0,0 +1,173 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define BUFFERS_PRIVATE // XXXX remove. +#define PROTO_HTTP_PRIVATE +#include "or.h" +#include "buffers.h" +#include "proto_http.h" + +/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ +int +peek_buf_has_http_command(const buf_t *buf) +{ + if (peek_buf_startswith(buf, "CONNECT ") || + peek_buf_startswith(buf, "DELETE ") || + peek_buf_startswith(buf, "GET ") || + peek_buf_startswith(buf, "POST ") || + peek_buf_startswith(buf, "PUT " )) + return 1; + return 0; +} + +/** There is a (possibly incomplete) http statement on <b>buf</b>, of the + * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) + * If a) the headers include a Content-Length field and all bytes in + * the body are present, or b) there's no Content-Length field and + * all headers are present, then: + * + * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. + * - memdup body into <b>*body_out</b>, and NUL-terminate it. + * - Then remove them from <b>buf</b>, and return 1. + * + * - If headers or body is NULL, discard that part of the buf. + * - If a headers or body doesn't fit in the arg, return -1. + * (We ensure that the headers or body don't exceed max len, + * _even if_ we're planning to discard them.) + * - If force_complete is true, then succeed even if not all of the + * content has arrived. + * + * Else, change nothing and return 0. + */ +int +fetch_from_buf_http(buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + char *headers; + size_t headerlen, bodylen, contentlen=0; + int crlf_offset; + int r; + + if (!buf->head) + return 0; + + crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); + if (crlf_offset > (int)max_headerlen || + (crlf_offset < 0 && buf->datalen > max_headerlen)) { + log_debug(LD_HTTP,"headers too long."); + return -1; + } else if (crlf_offset < 0) { + log_debug(LD_HTTP,"headers not all here yet."); + return 0; + } + /* Okay, we have a full header. Make sure it all appears in the first + * chunk. */ + if ((int)buf->head->datalen < crlf_offset + 4) + buf_pullup(buf, crlf_offset+4); + headerlen = crlf_offset + 4; + + headers = buf->head->data; + bodylen = buf->datalen - headerlen; + log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); + + if (max_headerlen <= headerlen) { + log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", + (int)headerlen, (int)max_headerlen-1); + return -1; + } + if (max_bodylen <= bodylen) { + log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", + (int)bodylen, (int)max_bodylen-1); + return -1; + } + + r = buf_http_find_content_length(headers, headerlen, &contentlen); + if (r == -1) { + log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " + "someone is trying to crash us."); + return -1; + } else if (r == 1) { + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } else { + tor_assert(r == 0); + /* Leave bodylen alone */ + } + + /* all happy. copy into the appropriate places, and return 1 */ + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + fetch_from_buf(*headers_out, headerlen, buf); + (*headers_out)[headerlen] = 0; /* NUL terminate it */ + } + if (body_out) { + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + fetch_from_buf(*body_out, bodylen, buf); + (*body_out)[bodylen] = 0; /* NUL terminate it */ + } + return 1; +} + +/** + * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at + * <b>headers</b>, looking for a "Content-Length" header. Try to set + * *<b>result_out</b> to the numeric value of that header if possible. + * Return -1 if the header was malformed, 0 if it was missing, and 1 if + * it was present and well-formed. + */ +STATIC int +buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out) +{ + const char *p, *newline; + char *len_str, *eos=NULL; + size_t remaining, result; + int ok; + *result_out = 0; /* The caller shouldn't look at this unless the + * return value is 1, but let's prevent confusion */ + +#define CONTENT_LENGTH "\r\nContent-Length: " + p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); + if (p == NULL) + return 0; + + tor_assert(p >= headers && p < headers+headerlen); + remaining = (headers+headerlen)-p; + p += strlen(CONTENT_LENGTH); + remaining -= strlen(CONTENT_LENGTH); + + newline = memchr(p, '\n', remaining); + if (newline == NULL) + return -1; + + len_str = tor_memdup_nulterm(p, newline-p); + /* We limit the size to INT_MAX because other parts of the buffer.c + * code don't like buffers to be any bigger than that. */ + result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); + if (eos && !tor_strisspace(eos)) { + ok = 0; + } else { + *result_out = result; + } + tor_free(len_str); + + return ok ? 1 : -1; +} + |