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
This commit is contained in:
Scott Griepentrog 2014-01-17 20:51:19 +00:00
parent 926081461b
commit 2704b49c1b
1 changed files with 185 additions and 57 deletions

View File

@ -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,35 +920,11 @@ struct ast_variable *ast_http_get_post_vars(
return NULL;
}
content_length = get_content_length(headers);
if (content_length <= 0) {
buf = ast_http_get_contents(&content_length, ser, headers);
if (buf == NULL) {
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;
return NULL;
}
buf[content_length] = '\0';
while ((val = strsep(&buf, "&"))) {
var = strsep(&val, "=");
if (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;