536 lines
13 KiB
C
536 lines
13 KiB
C
#include "include/conf.h"
|
|
|
|
#include "include/http.h"
|
|
#include "include/httpbody.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
bool vw_HTTPBody_init(vw_HTTPBody *body, vs_Stream *stream, size_t datasz, bool chunked) {
|
|
if (!body || !stream) {
|
|
return false;
|
|
}
|
|
|
|
body->stream = stream;
|
|
body->datasz = datasz;
|
|
body->chunked = chunked;
|
|
body->finished = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool vw_HTTPBody_read1(vw_HTTPBody *body, void *buf) {
|
|
if (!body || body->finished) {
|
|
return false;
|
|
}
|
|
|
|
if (body->datasz == 0) {
|
|
if (body->chunked) {
|
|
bool found = false;
|
|
char hex[20];
|
|
|
|
if (vs_Stream_ignore_err(vs_Stream_readb(body->stream,
|
|
hex,
|
|
sizeof(hex),
|
|
VW_HTTP_CRLF,
|
|
VW_HTTP_CRLF_LENGTH,
|
|
&found,
|
|
true)) == 0 ||
|
|
!found) {
|
|
return false;
|
|
}
|
|
|
|
body->datasz = vs_hex2usize(hex);
|
|
|
|
if (body->datasz == 0) {
|
|
body->finished = true;
|
|
vs_Stream_read(body->stream, NULL, VW_HTTP_CRLF_LENGTH);
|
|
return false;
|
|
}
|
|
} else {
|
|
body->finished = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const bool ret = vs_Stream_read1(body->stream, buf);
|
|
|
|
if (ret) {
|
|
--body->datasz;
|
|
|
|
if (body->datasz == 0 &&
|
|
vs_Stream_read(body->stream, NULL, VW_HTTP_CRLF_LENGTH) != VW_HTTP_CRLF_LENGTH) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
size_t vw_HTTPBody_read(vw_HTTPBody *body, void *buf, size_t count) {
|
|
if (!body) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
if (body->finished) {
|
|
return 0;
|
|
}
|
|
|
|
size_t total_read = 0;
|
|
|
|
do {
|
|
if (body->datasz == 0) {
|
|
if (body->chunked) {
|
|
bool found = false;
|
|
char hex[20];
|
|
|
|
if (vs_Stream_ignore_err(vs_Stream_readb(body->stream,
|
|
hex,
|
|
sizeof(hex),
|
|
VW_HTTP_CRLF,
|
|
VW_HTTP_CRLF_LENGTH,
|
|
&found,
|
|
true)) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
|
|
body->datasz = vs_hex2usize(hex);
|
|
|
|
if (body->datasz == 0) {
|
|
body->finished = true;
|
|
return vs_Stream_read(body->stream, NULL, VW_HTTP_CRLF_LENGTH)
|
|
? total_read
|
|
: VS_STREAM_ERROR;
|
|
}
|
|
} else {
|
|
body->finished = true;
|
|
return total_read;
|
|
}
|
|
}
|
|
|
|
const size_t now_read = vs_Stream_read(body->stream,
|
|
buf ? ((uint8_t *)buf + total_read) : NULL,
|
|
VS_MIN(body->datasz, count - total_read));
|
|
|
|
if (vs_Stream_ignore_err(now_read) == 0) {
|
|
break;
|
|
}
|
|
|
|
body->datasz -= now_read;
|
|
total_read += now_read;
|
|
|
|
if (body->datasz == 0) {
|
|
if (body->chunked) {
|
|
/* End of chunk */
|
|
if (vs_Stream_read(body->stream, NULL, VW_HTTP_CRLF_LENGTH) !=
|
|
VW_HTTP_CRLF_LENGTH) {
|
|
break;
|
|
}
|
|
} else {
|
|
/* End of data */
|
|
body->finished = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (total_read < count);
|
|
|
|
return total_read;
|
|
}
|
|
|
|
size_t vw_HTTPBody_readb(vw_HTTPBody *body,
|
|
void *buf,
|
|
size_t buf_size,
|
|
const void *boundary,
|
|
size_t boundary_size,
|
|
bool *found,
|
|
bool is_string) {
|
|
if (found) {
|
|
*found = false;
|
|
}
|
|
|
|
if (!body || !boundary) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
if (body->finished) {
|
|
return 0;
|
|
}
|
|
|
|
size_t total_read = 0;
|
|
size_t matched_bytes = 0;
|
|
|
|
const size_t full_read_size = buf_size - is_string;
|
|
|
|
do {
|
|
if (body->datasz == 0) {
|
|
if (body->chunked) {
|
|
bool read_found = false;
|
|
char hex[20];
|
|
|
|
if (vs_Stream_ignore_err(vs_Stream_readb(body->stream,
|
|
hex,
|
|
sizeof(hex),
|
|
VW_HTTP_CRLF,
|
|
VW_HTTP_CRLF_LENGTH,
|
|
&read_found,
|
|
true)) == 0) {
|
|
return false;
|
|
}
|
|
if (!read_found) {
|
|
return false;
|
|
}
|
|
|
|
body->datasz = vs_hex2usize(hex);
|
|
|
|
if (body->datasz == 0) {
|
|
body->finished = true;
|
|
return vs_Stream_read(body->stream, NULL, VW_HTTP_CRLF_LENGTH)
|
|
? total_read
|
|
: VS_STREAM_ERROR;
|
|
}
|
|
} else {
|
|
body->finished = true;
|
|
return total_read;
|
|
}
|
|
}
|
|
|
|
uint8_t byte = 0;
|
|
|
|
if (!vs_Stream_read1(body->stream, &byte)) {
|
|
break;
|
|
}
|
|
|
|
--body->datasz;
|
|
|
|
if (is_string && byte == '\0') {
|
|
return total_read;
|
|
}
|
|
|
|
if (byte == *((const uint8_t *)boundary + matched_bytes)) {
|
|
++matched_bytes;
|
|
|
|
if (matched_bytes == boundary_size) {
|
|
const size_t total = total_read - matched_bytes + 1;
|
|
|
|
if (found) {
|
|
*found = true;
|
|
}
|
|
|
|
if (is_string && buf) {
|
|
*((uint8_t *)buf + total) = '\0';
|
|
}
|
|
|
|
return total;
|
|
}
|
|
} else {
|
|
matched_bytes = 0;
|
|
}
|
|
|
|
if (buf) {
|
|
*((uint8_t *)buf + total_read) = byte;
|
|
}
|
|
|
|
++total_read;
|
|
|
|
if (body->datasz == 0) {
|
|
if (body->chunked) {
|
|
/* End of chunk */
|
|
if (vs_Stream_read(body->stream, NULL, VW_HTTP_CRLF_LENGTH) !=
|
|
VW_HTTP_CRLF_LENGTH) {
|
|
break;
|
|
}
|
|
} else {
|
|
/* End of data */
|
|
body->finished = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (total_read < full_read_size);
|
|
|
|
if (is_string) {
|
|
if (buf) {
|
|
*((uint8_t *)buf + total_read) = '\0';
|
|
}
|
|
|
|
++total_read;
|
|
}
|
|
|
|
return total_read;
|
|
}
|
|
|
|
size_t vw_HTTPBody_readbf(vw_HTTPBody *body,
|
|
void *buf,
|
|
size_t buf_size,
|
|
bool *found,
|
|
bool is_string,
|
|
const char *boundary_fmt,
|
|
...) {
|
|
if (!body) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
if (body->finished || !boundary_fmt) {
|
|
return 0;
|
|
}
|
|
|
|
va_list args;
|
|
char *temp_buf = NULL;
|
|
size_t size = 0;
|
|
ssize_t needed = 0;
|
|
|
|
va_start(args, boundary_fmt);
|
|
|
|
/* Determine the size needed for the buffer */
|
|
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
needed = vsnprintf(NULL, 0, boundary_fmt, args_copy) + 1; /* +1 for null terminator */
|
|
va_end(args_copy);
|
|
|
|
if (needed <= 0) {
|
|
va_end(args);
|
|
return 0;
|
|
}
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Alocate a dynamic temporary buffer */
|
|
|
|
temp_buf = VS_MALLOC(size);
|
|
|
|
if (!temp_buf) {
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
/* Format the string into the allocated buffer */
|
|
|
|
needed = vsnprintf(temp_buf, size, boundary_fmt, args);
|
|
va_end(args);
|
|
|
|
if (needed < 0) {
|
|
VS_FREE(temp_buf);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Use readb() abstraction */
|
|
|
|
size = vw_HTTPBody_readb(body, buf, buf_size, temp_buf, size, found, is_string);
|
|
VS_FREE(temp_buf);
|
|
return size;
|
|
}
|
|
|
|
size_t vw_HTTPBody_write(vw_HTTPBody *body, const void *buf, size_t count) {
|
|
if (!body) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (!buf || count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return vs_Stream_write(body->stream, buf, count);
|
|
}
|
|
|
|
size_t vw_HTTPBody_write_chunk(vw_HTTPBody *body, const void *buf, size_t count) {
|
|
if (!body) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (!buf || count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (vs_Stream_ignore_err(vs_Stream_writef(body->stream, "%zX" VW_HTTP_CRLF, count)) <
|
|
VW_HTTP_CRLF_LENGTH + 1) {
|
|
return 0;
|
|
}
|
|
|
|
const size_t written = vs_Stream_write(body->stream, buf, count);
|
|
|
|
if (vs_Stream_write(body->stream, VW_HTTP_CRLF, VW_HTTP_CRLF_LENGTH) != VW_HTTP_CRLF_LENGTH) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
size_t vw_HTTPBody_writef(vw_HTTPBody *body, const char *fmt, ...) {
|
|
if (!body) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (!fmt || !*fmt) {
|
|
return 0;
|
|
}
|
|
|
|
va_list args;
|
|
char *temp_buf = NULL;
|
|
size_t size = 0;
|
|
ssize_t needed = 0;
|
|
|
|
va_start(args, fmt);
|
|
|
|
/* Determine the size needed for the buffer */
|
|
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
needed = vsnprintf(NULL, 0, fmt, args_copy) + 1; /* +1 for null terminator */
|
|
va_end(args_copy);
|
|
|
|
if (needed <= 0) {
|
|
va_end(args);
|
|
return 0;
|
|
}
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Alocate a dynamic temporary buffer */
|
|
|
|
temp_buf = VS_MALLOC(size);
|
|
|
|
if (!temp_buf) {
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
/* Format the string into the allocated buffer */
|
|
|
|
needed = vsnprintf(temp_buf, size, fmt, args);
|
|
|
|
if (needed < 0) {
|
|
VS_FREE(temp_buf);
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Now, just use the write abstraction */
|
|
|
|
size = vw_HTTPBody_write(body, temp_buf, size);
|
|
VS_FREE(temp_buf);
|
|
return size;
|
|
}
|
|
|
|
size_t vw_HTTPBody_writef_chunk(vw_HTTPBody *body, const char *fmt, ...) {
|
|
if (!body) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
if (!fmt) {
|
|
return 0;
|
|
}
|
|
|
|
va_list args;
|
|
char *temp_buf = NULL;
|
|
size_t size = 0;
|
|
ssize_t needed = 0;
|
|
|
|
va_start(args, fmt);
|
|
|
|
/* Determine the size needed for the buffer */
|
|
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
needed = vsnprintf(NULL, 0, fmt, args_copy) + 1; /* +1 for null terminator */
|
|
va_end(args_copy);
|
|
|
|
if (needed <= 0) {
|
|
va_end(args);
|
|
return 0;
|
|
}
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Alocate a dynamic temporary buffer */
|
|
|
|
temp_buf = VS_MALLOC(size);
|
|
|
|
if (!temp_buf) {
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
/* Format the string into the allocated buffer */
|
|
|
|
needed = vsnprintf(temp_buf, size, fmt, args);
|
|
|
|
if (needed < 0) {
|
|
VS_FREE(temp_buf);
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Now, just use the write abstraction */
|
|
|
|
if (vs_Stream_ignore_err(vw_HTTPBody_writef(body, "%zX" VW_HTTP_CRLF, size)) <
|
|
VW_HTTP_CRLF_LENGTH + 1) {
|
|
size = VS_STREAM_ERROR;
|
|
} else {
|
|
size = vw_HTTPBody_write(body, temp_buf, size);
|
|
|
|
if (vs_Stream_ignore_err(size) != 0 &&
|
|
vs_Stream_write(body->stream, VW_HTTP_CRLF, VW_HTTP_CRLF_LENGTH) !=
|
|
VW_HTTP_CRLF_LENGTH) {
|
|
size = VS_STREAM_ERROR;
|
|
}
|
|
}
|
|
|
|
VS_FREE(temp_buf);
|
|
return size;
|
|
}
|
|
|
|
size_t vw_HTTPBody_skip_content(vw_HTTPBody *body) {
|
|
if (!body || body->finished) {
|
|
return 0;
|
|
}
|
|
|
|
size_t now_read = 0;
|
|
size_t total_read = 0;
|
|
|
|
while (!body->finished) {
|
|
now_read = vw_HTTPBody_read(body, NULL, body->datasz);
|
|
|
|
if (now_read == VS_STREAM_ERROR) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
total_read += now_read;
|
|
}
|
|
|
|
return total_read;
|
|
}
|
|
|
|
bool vw_HTTPBody_adjust_chunkiness(vw_HTTPBody *body, bool chunked, size_t datasz) {
|
|
if (!body || vw_HTTPBody_skip_content(body) == VS_STREAM_ERROR) {
|
|
return false;
|
|
}
|
|
|
|
body->chunked = chunked;
|
|
body->finished = false;
|
|
body->datasz = datasz;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool vw_HTTPBody_destroy(vw_HTTPBody *body) {
|
|
if (!body) {
|
|
return false;
|
|
}
|
|
|
|
body->stream = NULL;
|
|
body->datasz = 0;
|
|
body->chunked = false;
|
|
body->finished = false;
|
|
|
|
return true;
|
|
}
|