summaryrefslogtreecommitdiff
path: root/main/http.c
diff options
context:
space:
mode:
authorScott Griepentrog <sgriepentrog@digium.com>2014-01-17 20:51:19 +0000
committerScott Griepentrog <sgriepentrog@digium.com>2014-01-17 20:51:19 +0000
commit2704b49c1b038ffd4a6a413b9973814bee7a42b3 (patch)
treec5673014f2f17e28a912e064cc7efc94671677df /main/http.c
parent926081461b87e61529e03f133b22cf21a6cafa40 (diff)
http: supported chunked Transfer-Encoding
This change implements support for HTTP Transfer-Encoding chunked in both JSON and Form (post vars) body content. A new function ast_http_get_contents() handles both regular and chunked mode body, returning after the entire body is received. (closes issue ASTERISK-23068) Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/3125/ ........ Merged revisions 405861 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@405862 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main/http.c')
-rw-r--r--main/http.c242
1 files changed, 185 insertions, 57 deletions
diff --git a/main/http.c b/main/http.c
index af1ef81c2..e867de13a 100644
--- a/main/http.c
+++ b/main/http.c
@@ -688,11 +688,187 @@ static const char *get_transfer_encoding(struct ast_variable *headers)
return get_header(headers, "Transfer-Encoding");
}
+/*!
+ * \brief decode chunked mode hexadecimal value
+ *
+ * \param s string to decode
+ * \param len length of string
+ * \return integer value or -1 for decode error
+ */
+static int chunked_atoh(const char *s, int len)
+{
+ int value = 0;
+ char c;
+
+ if (*s < '0') {
+ /* zero value must be 0\n not just \n */
+ return -1;
+ }
+
+ while (len--)
+ {
+ if (*s == '\x0D') {
+ return value;
+ }
+ value <<= 4;
+ c = *s++;
+ if (c >= '0' && c <= '9') {
+ value += c - '0';
+ continue;
+ }
+ if (c >= 'a' && c <= 'f') {
+ value += 10 + c - 'a';
+ continue;
+ }
+ if (c >= 'A' && c <= 'F') {
+ value += 10 + c - 'A';
+ continue;
+ }
+ /* invalid character */
+ return -1;
+ }
+ /* end of string */
+ return -1;
+}
+
+/*!
+ * \brief Returns the contents (body) of the HTTP request
+ *
+ * \param return_length ptr to int that returns content length
+ * \param aser HTTP TCP/TLS session object
+ * \param headers List of HTTP headers
+ * \return ptr to content (zero terminated) or NULL on failure
+ * \note Since returned ptr is malloc'd, it should be free'd by caller
+ */
+static char *ast_http_get_contents(int *return_length,
+ struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
+{
+ const char *transfer_encoding;
+ int res;
+ int content_length = 0;
+ int chunk_length;
+ char chunk_header[8];
+ int bufsize = 250;
+ char *buf;
+
+ transfer_encoding = get_transfer_encoding(headers);
+
+ if (ast_strlen_zero(transfer_encoding) ||
+ strcasecmp(transfer_encoding, "chunked") != 0) {
+ /* handle regular non-chunked content */
+ content_length = get_content_length(headers);
+ if (content_length <= 0) {
+ /* no content - not an error */
+ return NULL;
+ }
+ if (content_length > MAX_POST_CONTENT - 1) {
+ ast_log(LOG_WARNING,
+ "Excessively long HTTP content. (%d > %d)\n",
+ content_length, MAX_POST_CONTENT);
+ errno = EFBIG;
+ return NULL;
+ }
+ buf = ast_malloc(content_length + 1);
+ if (!buf) {
+ /* Malloc sets ENOMEM */
+ return NULL;
+ }
+ res = fread(buf, 1, content_length, ser->f);
+ if (res < content_length) {
+ /* Error, distinguishable by ferror() or feof(), but neither
+ * is good. Treat either one as I/O error */
+ ast_log(LOG_WARNING, "Short HTTP request body (%d < %d)\n",
+ res, content_length);
+ errno = EIO;
+ ast_free(buf);
+ return NULL;
+ }
+ buf[content_length] = 0;
+ *return_length = content_length;
+ return buf;
+ }
+
+ /* pre-allocate buffer */
+ buf = ast_malloc(bufsize);
+ if (!buf) {
+ return NULL;
+ }
+
+ /* handled chunked content */
+ do {
+ /* get the line of hexadecimal giving chunk size */
+ if (!fgets(chunk_header, sizeof(chunk_header), ser->f)) {
+ ast_log(LOG_WARNING,
+ "Short HTTP read of chunked header\n");
+ errno = EIO;
+ ast_free(buf);
+ return NULL;
+ }
+ chunk_length = chunked_atoh(chunk_header, sizeof(chunk_header));
+ if (chunk_length < 0) {
+ ast_log(LOG_WARNING, "Invalid HTTP chunk size\n");
+ errno = EIO;
+ ast_free(buf);
+ return NULL;
+ }
+ if (content_length + chunk_length > MAX_POST_CONTENT - 1) {
+ ast_log(LOG_WARNING,
+ "Excessively long HTTP chunk. (%d + %d > %d)\n",
+ content_length, chunk_length, MAX_POST_CONTENT);
+ errno = EFBIG;
+ ast_free(buf);
+ return NULL;
+ }
+
+ /* insure buffer is large enough +1 */
+ if (content_length + chunk_length >= bufsize)
+ {
+ bufsize *= 2;
+ buf = ast_realloc(buf, bufsize);
+ if (!buf) {
+ return NULL;
+ }
+ }
+
+ /* read the chunk */
+ res = fread(buf + content_length, 1, chunk_length, ser->f);
+ if (res < chunk_length) {
+ ast_log(LOG_WARNING, "Short HTTP chunk read (%d < %d)\n",
+ res, chunk_length);
+ errno = EIO;
+ ast_free(buf);
+ return NULL;
+ }
+ content_length += chunk_length;
+
+ /* insure the next 2 bytes are CRLF */
+ res = fread(chunk_header, 1, 2, ser->f);
+ if (res < 2) {
+ ast_log(LOG_WARNING,
+ "Short HTTP chunk sync read (%d < 2)\n", res);
+ errno = EIO;
+ ast_free(buf);
+ return NULL;
+ }
+ if (chunk_header[0] != 0x0D || chunk_header[1] != 0x0A) {
+ ast_log(LOG_WARNING,
+ "Post HTTP chunk sync bytes wrong (%d, %d)\n",
+ chunk_header[0], chunk_header[1]);
+ errno = EIO;
+ ast_free(buf);
+ return NULL;
+ }
+ } while (chunk_length);
+
+ buf[content_length] = 0;
+ *return_length = content_length;
+ return buf;
+}
+
struct ast_json *ast_http_get_json(
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
int content_length = 0;
- int res;
struct ast_json *body;
RAII_VAR(char *, buf, NULL, ast_free);
RAII_VAR(char *, type, get_content_type(headers), ast_free);
@@ -705,34 +881,10 @@ struct ast_json *ast_http_get_json(
return NULL;
}
- content_length = get_content_length(headers);
-
- if (content_length <= 0) {
- /* No content (or streaming content). */
- return NULL;
- }
-
- if (content_length > MAX_POST_CONTENT - 1) {
- ast_log(LOG_WARNING,
- "Excessively long HTTP content. (%d > %d)\n",
- content_length, MAX_POST_CONTENT);
- errno = EFBIG;
- return NULL;
- }
-
- buf = ast_malloc(content_length);
- if (!buf) {
- /* Malloc sets ENOMEM */
- return NULL;
- }
-
- res = fread(buf, 1, content_length, ser->f);
- if (res < content_length) {
- /* Error, distinguishable by ferror() or feof(), but neither
- * is good. Treat either one as I/O error */
- ast_log(LOG_WARNING, "Short HTTP request body (%d < %d)\n",
- res, content_length);
- errno = EIO;
+ buf = ast_http_get_contents(&content_length, ser, headers);
+ if (buf == NULL)
+ {
+ /* errno already set */
return NULL;
}
@@ -758,7 +910,6 @@ struct ast_variable *ast_http_get_post_vars(
char *var, *val;
RAII_VAR(char *, buf, NULL, ast_free_ptr);
RAII_VAR(char *, type, get_content_type(headers), ast_free);
- int res;
/* Use errno to distinguish errors from no params */
errno = 0;
@@ -769,34 +920,10 @@ struct ast_variable *ast_http_get_post_vars(
return NULL;
}
- content_length = get_content_length(headers);
-
- if (content_length <= 0) {
- return NULL;
- }
-
- if (content_length > MAX_POST_CONTENT - 1) {
- ast_log(LOG_WARNING,
- "Excessively long HTTP content. (%d > %d)\n",
- content_length, MAX_POST_CONTENT);
- errno = EFBIG;
- return NULL;
- }
-
- buf = ast_malloc(content_length + 1);
- if (!buf) {
- /* malloc sets errno to ENOMEM */
- return NULL;
- }
-
- res = fread(buf, 1, content_length, ser->f);
- if (res < content_length) {
- /* Error, distinguishable by ferror() or feof(), but neither
- * is good. Treat either one as I/O error */
- errno = EIO;
+ buf = ast_http_get_contents(&content_length, ser, headers);
+ if (buf == NULL) {
return NULL;
}
- buf[content_length] = '\0';
while ((val = strsep(&buf, "&"))) {
var = strsep(&val, "=");
@@ -1191,7 +1318,8 @@ static void *httpd_helper_thread(void *data)
* RFC 2616, section 3.6, we should respond with a 501 for any transfer-
* codings we don't understand.
*/
- if (strcasecmp(transfer_encoding, "identity") != 0) {
+ if (strcasecmp(transfer_encoding, "identity") != 0 &&
+ strcasecmp(transfer_encoding, "chunked") != 0) {
/* Transfer encodings not supported */
ast_http_error(ser, 501, "Unimplemented", "Unsupported Transfer-Encoding.");
goto done;