mirror of
https://gitlab.xiph.org/xiph/icecast-common.git
synced 2024-09-01 03:54:15 -04:00
838 lines
24 KiB
C
838 lines
24 KiB
C
/* encoding.c
|
|
**
|
|
** http transfer encoding library
|
|
** See RFC2616 section 3.6 for more details.
|
|
**
|
|
** Copyright (C) 2015-2018 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
|
|
**
|
|
** This library is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU Library General Public
|
|
** License as published by the Free Software Foundation; either
|
|
** version 2 of the License, or (at your option) any later version.
|
|
**
|
|
** This library is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
** Library General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU Library General Public
|
|
** License along with this library; if not, write to the
|
|
** Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
** Boston, MA 02110-1301, USA.
|
|
**
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "encoding.h"
|
|
|
|
struct igloo_httpp_encoding_tag {
|
|
size_t refc;
|
|
|
|
ssize_t (*process_read)(igloo_httpp_encoding_t *self, void *buf, size_t len, ssize_t (*cb)(void*, void*, size_t), void *userdata);
|
|
ssize_t (*process_write)(igloo_httpp_encoding_t *self, const void *buf, size_t len, ssize_t (*cb)(void*, const void*, size_t), void *userdata);
|
|
|
|
httpp_meta_t *meta_read;
|
|
httpp_meta_t *meta_write;
|
|
|
|
void *buf_read_raw; /* input buffer */
|
|
size_t buf_read_raw_offset, buf_read_raw_len;
|
|
|
|
void *buf_read_decoded; /* decoded stuff */
|
|
size_t buf_read_decoded_offset, buf_read_decoded_len;
|
|
|
|
void *buf_write_raw; /* input buffer */
|
|
size_t buf_write_raw_offset, buf_write_raw_len;
|
|
|
|
void *buf_write_encoded; /* encoded output */
|
|
size_t buf_write_encoded_offset, buf_write_encoded_len;
|
|
|
|
/* backend specific stuff */
|
|
ssize_t bytes_till_eof;
|
|
size_t read_bytes_till_header;
|
|
};
|
|
|
|
|
|
/* Handlers, at end of file */
|
|
static ssize_t igloo___enc_identity_read(igloo_httpp_encoding_t *self, void *buf, size_t len, ssize_t (*cb)(void*, void*, size_t), void *userdata);
|
|
static ssize_t igloo___enc_identity_write(igloo_httpp_encoding_t *self, const void *buf, size_t len, ssize_t (*cb)(void*, const void*, size_t), void *userdata);
|
|
static ssize_t igloo___enc_chunked_read(igloo_httpp_encoding_t *self, void *buf, size_t len, ssize_t (*cb)(void*, void*, size_t), void *userdata);
|
|
static ssize_t igloo___enc_chunked_write(igloo_httpp_encoding_t *self, const void *buf, size_t len, ssize_t (*cb)(void*, const void*, size_t), void *userdata);
|
|
|
|
/* function to move some data out of our buffers */
|
|
ssize_t igloo___copy_buffer(void *dst, void **src, size_t *boffset, size_t *blen, size_t len)
|
|
{
|
|
void *p;
|
|
size_t have_len;
|
|
size_t todo;
|
|
|
|
if (!len)
|
|
return 0;
|
|
|
|
if (!dst || !src || !*src || !boffset || !blen)
|
|
return -1;
|
|
|
|
have_len = *blen - *boffset;
|
|
p = *src + *boffset;
|
|
|
|
todo = len < have_len ? len : have_len;
|
|
|
|
memcpy(dst, p, todo);
|
|
|
|
*boffset += todo;
|
|
|
|
if (*boffset == *blen) {
|
|
free(*src);
|
|
*src = NULL;
|
|
*boffset = 0;
|
|
*blen = 0;
|
|
}
|
|
|
|
return todo;
|
|
}
|
|
|
|
/* try to flush output buffers */
|
|
static inline void __flush_output(igloo_httpp_encoding_t *self, ssize_t (*cb)(void*, const void*, size_t), void *userdata)
|
|
{
|
|
if (cb && self->buf_write_encoded) {
|
|
ssize_t ret = cb(userdata,
|
|
self->buf_write_encoded + self->buf_write_encoded_offset,
|
|
self->buf_write_encoded_len - self->buf_write_encoded_offset);
|
|
if (ret > 0) {
|
|
self->buf_write_encoded_offset += ret;
|
|
if (self->buf_write_encoded_offset == self->buf_write_encoded_len) {
|
|
free(self->buf_write_encoded);
|
|
self->buf_write_encoded = NULL;
|
|
self->buf_write_encoded_offset = 0;
|
|
self->buf_write_encoded_len = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* meta data functions */
|
|
/* meta data is to be used in a encoding-specific way */
|
|
httpp_meta_t *igloo_httpp_encoding_meta_new(const char *key, const char *value)
|
|
{
|
|
httpp_meta_t *ret = calloc(1, sizeof(httpp_meta_t));
|
|
if (!ret)
|
|
return ret;
|
|
|
|
if (key)
|
|
if ((ret->key = strdup(key)) == NULL)
|
|
goto fail;
|
|
|
|
if (value) {
|
|
if ((ret->value = strdup(value)) == NULL)
|
|
goto fail;
|
|
ret->value_len = strlen(ret->value);
|
|
}
|
|
|
|
return ret;
|
|
fail:
|
|
igloo_httpp_encoding_meta_free(ret);
|
|
return NULL;
|
|
}
|
|
|
|
int igloo_httpp_encoding_meta_free(httpp_meta_t *self)
|
|
{
|
|
while (self) {
|
|
httpp_meta_t *cur = self;
|
|
self = self->next;
|
|
|
|
if (cur->key)
|
|
free(cur->key);
|
|
if (cur->value)
|
|
free(cur->value);
|
|
free(cur);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int igloo_httpp_encoding_meta_append(httpp_meta_t **dst, httpp_meta_t *next)
|
|
{
|
|
httpp_meta_t **cur;
|
|
|
|
if (!dst)
|
|
return -1;
|
|
if (!next)
|
|
return 0;
|
|
|
|
cur = dst;
|
|
while (*cur)
|
|
cur = &((*cur)->next);
|
|
|
|
*cur = next;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* General setup */
|
|
igloo_httpp_encoding_t *igloo_httpp_encoding_new(const char *encoding) {
|
|
igloo_httpp_encoding_t *ret = calloc(1, sizeof(igloo_httpp_encoding_t));
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
ret->refc = 1;
|
|
ret->bytes_till_eof = -1;
|
|
|
|
if (strcasecmp(encoding, igloo_HTTPP_ENCODING_IDENTITY) == 0) {
|
|
ret->process_read = igloo___enc_identity_read;
|
|
ret->process_write = igloo___enc_identity_write;
|
|
} else if (strcasecmp(encoding, igloo_HTTPP_ENCODING_CHUNKED) == 0) {
|
|
ret->process_read = igloo___enc_chunked_read;
|
|
ret->process_write = igloo___enc_chunked_write;
|
|
} else {
|
|
goto fail;
|
|
}
|
|
|
|
return ret;
|
|
|
|
fail:
|
|
igloo_httpp_encoding_release(ret);
|
|
return NULL;
|
|
}
|
|
|
|
int igloo_httpp_encoding_addref(igloo_httpp_encoding_t *self)
|
|
{
|
|
if (!self)
|
|
return -1;
|
|
self->refc++;
|
|
return 0;
|
|
}
|
|
|
|
int igloo_httpp_encoding_release(igloo_httpp_encoding_t *self)
|
|
{
|
|
if (!self)
|
|
return -1;
|
|
|
|
self->refc--;
|
|
if (self->refc)
|
|
return 0;
|
|
|
|
igloo_httpp_encoding_meta_free(self->meta_read);
|
|
igloo_httpp_encoding_meta_free(self->meta_write);
|
|
|
|
if (self->buf_read_raw)
|
|
free(self->buf_read_raw);
|
|
if (self->buf_read_decoded)
|
|
free(self->buf_read_decoded);
|
|
if (self->buf_write_raw)
|
|
free(self->buf_write_raw);
|
|
if (self->buf_write_encoded)
|
|
free(self->buf_write_encoded);
|
|
free(self);
|
|
return 0;
|
|
}
|
|
|
|
/* Read data from backend.
|
|
* if cb is NULL this will read from the internal buffer.
|
|
*/
|
|
ssize_t igloo_httpp_encoding_read(igloo_httpp_encoding_t *self, void *buf, size_t len, ssize_t (*cb)(void*, void*, size_t), void *userdata)
|
|
{
|
|
ssize_t done = 0;
|
|
ssize_t ret;
|
|
|
|
if (!self || !buf)
|
|
return -1;
|
|
|
|
if (!len)
|
|
return 0;
|
|
|
|
ret = igloo___copy_buffer(buf, &(self->buf_read_decoded), &(self->buf_read_decoded_offset), &(self->buf_read_decoded_len), len);
|
|
|
|
if (ret == (ssize_t)len)
|
|
return ret;
|
|
|
|
if (ret > 0) {
|
|
done += ret;
|
|
buf += ret;
|
|
len -= ret;
|
|
}
|
|
|
|
ret = self->process_read(self, buf, len, cb, userdata);
|
|
if (ret == -1 && done)
|
|
return done;
|
|
if (ret == -1)
|
|
return -1;
|
|
|
|
done += ret;
|
|
buf += ret;
|
|
len -= ret;
|
|
|
|
if (len) {
|
|
ret = igloo___copy_buffer(buf, &(self->buf_read_decoded), &(self->buf_read_decoded_offset), &(self->buf_read_decoded_len), len);
|
|
if (ret > 0) {
|
|
done += ret;
|
|
buf += ret;
|
|
len -= ret;
|
|
}
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
int igloo_httpp_encoding_eof(igloo_httpp_encoding_t *self, int (*cb)(void*), void *userdata)
|
|
{
|
|
if (!self)
|
|
return -1;
|
|
|
|
if (self->buf_read_decoded_len - self->buf_read_decoded_offset)
|
|
return 0;
|
|
|
|
if (self->bytes_till_eof == 0)
|
|
return 1;
|
|
|
|
if (cb)
|
|
return cb(userdata);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read any meta data that is in buffer.
|
|
* After a call to this function the meta data is released from the
|
|
* encoding object and the caller is responsible to free it.
|
|
*/
|
|
httpp_meta_t *igloo_httpp_encoding_get_meta(igloo_httpp_encoding_t *self)
|
|
{
|
|
httpp_meta_t *ret;
|
|
|
|
if (!self)
|
|
return NULL;
|
|
|
|
ret = self->meta_read;
|
|
self->meta_read = NULL;
|
|
return ret;
|
|
}
|
|
|
|
/* Write data to backend.
|
|
* If buf is NULL this will flush buffers.
|
|
* Depending on encoding flushing buffers may not be safe if not
|
|
* at end of stream.
|
|
*/
|
|
ssize_t igloo_httpp_encoding_write(igloo_httpp_encoding_t *self, const void *buf, size_t len, ssize_t (*cb)(void*, const void*, size_t), void *userdata)
|
|
{
|
|
ssize_t ret;
|
|
|
|
if (!self || !cb)
|
|
return -1;
|
|
|
|
/* first try to flush buffers */
|
|
__flush_output(self, cb, userdata);
|
|
|
|
/* now run the processor */
|
|
ret = self->process_write(self, buf, len, cb, userdata);
|
|
|
|
/* try to flush buffers again, maybe they are filled now! */
|
|
__flush_output(self, cb, userdata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Check if we have something to flush. */
|
|
ssize_t igloo_httpp_encoding_pending(igloo_httpp_encoding_t *self)
|
|
{
|
|
if (!self)
|
|
return -1;
|
|
if (!self->buf_write_encoded)
|
|
return 0;
|
|
return self->buf_write_encoded_len - self->buf_write_encoded_offset;
|
|
}
|
|
|
|
/* Attach meta data to the stream.
|
|
* this is to be written out as soon as the encoding supports.
|
|
*/
|
|
int igloo_httpp_encoding_append_meta(igloo_httpp_encoding_t *self, httpp_meta_t *meta)
|
|
{
|
|
if (!self)
|
|
return -1;
|
|
return igloo_httpp_encoding_meta_append(&(self->meta_write), meta);
|
|
}
|
|
|
|
/* handlers for encodings */
|
|
static ssize_t igloo___enc_identity_read(igloo_httpp_encoding_t *self, void *buf, size_t len, ssize_t (*cb)(void*, void*, size_t), void *userdata)
|
|
{
|
|
(void)self;
|
|
if (!cb)
|
|
return -1;
|
|
return cb(userdata, buf, len);
|
|
}
|
|
|
|
static ssize_t igloo___enc_identity_write(igloo_httpp_encoding_t *self, const void *buf, size_t len, ssize_t (*cb)(void*, const void*, size_t), void *userdata)
|
|
{
|
|
(void)self;
|
|
if (!cb)
|
|
return -1;
|
|
return cb(userdata, buf, len);
|
|
}
|
|
|
|
/* Here is what chunked encoding looks like:
|
|
*
|
|
* You have any number of chunks. They are just chained.
|
|
* The last chunk has a length of zero set.
|
|
* After the last chunk there are tailing headers and a final "\r\n".
|
|
*
|
|
* Each chunk looks like this:
|
|
* LENGTH as hex [";" extension [";" extention [...]]] "\r\n" BODY "\r\n".
|
|
* extension is: TOKEN ["=" data];
|
|
* data is: TOKEN | STRING as quoted.
|
|
*
|
|
*/
|
|
|
|
static void __enc_chunked_read_extentions(igloo_httpp_encoding_t *self, char *p, size_t len)
|
|
{
|
|
/* ok. If you want to ruin your day... go ahead and try to understand this. */
|
|
httpp_meta_t **parent;
|
|
httpp_meta_t *meta;
|
|
size_t value_len;
|
|
size_t value_decoded_len;
|
|
size_t key_len;
|
|
char *value;
|
|
char *c;
|
|
int in_quote;
|
|
|
|
if (self->meta_read) {
|
|
parent = &(self->meta_read);
|
|
while (*parent)
|
|
parent = &((*parent)->next);
|
|
} else {
|
|
parent = &(self->meta_read);
|
|
}
|
|
|
|
while (len) {
|
|
if (*p != ';') /* not a valid extension */
|
|
break;
|
|
p++;
|
|
len--;
|
|
|
|
*parent = meta = igloo_httpp_encoding_meta_new(NULL, NULL);
|
|
parent = &(meta->next);
|
|
|
|
for (key_len = 0; key_len < len && p[key_len] != '='; key_len++);
|
|
meta->key = malloc(key_len + 1);
|
|
if (!meta->key)
|
|
return;
|
|
memcpy(meta->key, p, key_len);
|
|
meta->key[key_len] = 0;
|
|
p += key_len;
|
|
len -= key_len;
|
|
if (!len)
|
|
break;
|
|
|
|
if (*p != '=') /* check if we have a value */
|
|
continue;
|
|
|
|
p++;
|
|
len--;
|
|
|
|
if (!len)
|
|
break;
|
|
|
|
in_quote = 0;
|
|
for (value_len = 0; value_len < len; value_len++) {
|
|
if (in_quote) {
|
|
if (p[value_len] == '\\') in_quote = 2;
|
|
else if (p[value_len] == '"') in_quote--;
|
|
if (in_quote)
|
|
continue;
|
|
value_len++;
|
|
break;
|
|
}
|
|
if (p[value_len] == '"') {
|
|
in_quote = 1;
|
|
} else if (p[value_len] == ';')
|
|
break;
|
|
}
|
|
|
|
value = meta->value = malloc(value_len + 1);
|
|
if (!meta->value)
|
|
return;
|
|
in_quote = 0;
|
|
for (c = p, p += value_len, len -= value_len, value_decoded_len = 0; value_len; value_len--, c++) {
|
|
if (in_quote) {
|
|
if (*c == '\\') in_quote = 2;
|
|
else if (*c == '"') {
|
|
in_quote--;
|
|
if (in_quote)
|
|
value[value_decoded_len++] = '"';
|
|
} else
|
|
value[value_decoded_len++] = *c;
|
|
if (in_quote)
|
|
continue;
|
|
break;
|
|
}
|
|
if (*c == '"') {
|
|
in_quote = 1;
|
|
} else if (p[value_len] == ';')
|
|
break;
|
|
else
|
|
value[value_decoded_len++] = *c;
|
|
}
|
|
value[value_decoded_len] = 0;
|
|
meta->value_len = value_decoded_len;
|
|
}
|
|
}
|
|
|
|
static ssize_t igloo___enc_chunked_read(igloo_httpp_encoding_t *self, void *buf, size_t len, ssize_t (*cb)(void*, void*, size_t), void *userdata)
|
|
{
|
|
ssize_t ret;
|
|
size_t buflen;
|
|
void *bufptr;
|
|
size_t i;
|
|
char *c;
|
|
int in_quote;
|
|
ssize_t offset_extentions;
|
|
ssize_t offset_CR;
|
|
ssize_t offset_LF;
|
|
long long unsigned int bodylen;
|
|
|
|
if (!cb)
|
|
return -1;
|
|
|
|
/* see if we have still a few bytes to go till the next header
|
|
* The 2 is the end of chunk mark that is not part of the body! */
|
|
if (self->read_bytes_till_header > 2) {
|
|
size_t todo = len > (self->read_bytes_till_header - 2) ? (self->read_bytes_till_header - 2) : len;
|
|
ret = cb(userdata, buf, todo);
|
|
if (ret < 1)
|
|
return ret;
|
|
self->read_bytes_till_header -= ret;
|
|
return ret;
|
|
}
|
|
|
|
/* ok. now we should have 2 or less bytes till next header.
|
|
* We just read a few bytes into our decoding buffer and see what we got.
|
|
*/
|
|
if (self->bytes_till_eof != -1) {
|
|
buflen = self->bytes_till_eof;
|
|
} else {
|
|
buflen = 1024;
|
|
}
|
|
|
|
if (self->buf_read_raw) {
|
|
bufptr = realloc(self->buf_read_raw, self->buf_read_raw_len + buflen);
|
|
} else {
|
|
bufptr = malloc(buflen);
|
|
self->buf_read_raw_offset = 0;
|
|
self->buf_read_raw_len = 0;
|
|
}
|
|
if (!bufptr)
|
|
return -1;
|
|
|
|
self->buf_read_raw = bufptr;
|
|
|
|
ret = cb(userdata, self->buf_read_raw + self->buf_read_raw_len, buflen);
|
|
if (ret < 1) {
|
|
if (!self->buf_read_raw_len) {
|
|
free(self->buf_read_raw);
|
|
self->buf_read_raw = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
self->buf_read_raw_len += ret;
|
|
|
|
/* now we should have some bytes in our buffer.
|
|
* Now skip the "\r\n" that may be left to do from self->read_bytes_till_header.
|
|
*/
|
|
|
|
if ((self->buf_read_raw_len - self->buf_read_raw_offset) == self->read_bytes_till_header) {
|
|
/* ok, that wasn't super big step forward. At least we got rid of self->read_bytes_till_header.
|
|
* now freeing buffers again...
|
|
*/
|
|
free(self->buf_read_raw);
|
|
self->buf_read_raw = NULL;
|
|
self->buf_read_raw_offset = 0;
|
|
self->buf_read_raw_len = 0;
|
|
self->read_bytes_till_header = 0;
|
|
return 0;
|
|
} else if ((self->buf_read_raw_len - self->buf_read_raw_offset) > self->read_bytes_till_header) {
|
|
/* ship the tailing "\r\n".
|
|
* We should check we really got that and not some other stuff, eh? Yes, I'm sure we should...
|
|
*/
|
|
self->buf_read_raw_offset += self->read_bytes_till_header;
|
|
self->read_bytes_till_header = 0;
|
|
}
|
|
|
|
/* Check if we reached EOF */
|
|
if (self->bytes_till_eof != -1) {
|
|
if (self->read_bytes_till_header == 0)
|
|
self->bytes_till_eof = 0;
|
|
|
|
if (self->bytes_till_eof == 0)
|
|
return 0;
|
|
}
|
|
|
|
/* ok. next we have at least a little bit if a header.
|
|
* Now we need to find out if the header is complet.
|
|
* If it is we will process it. If it isn't we will
|
|
* just return a short read and try again later!
|
|
*
|
|
* Hint: A complet header ends with "\r\n".
|
|
* But be aware! That is valid in any quoted string
|
|
* that is part of the header as well!
|
|
*/
|
|
|
|
in_quote = 0;
|
|
offset_extentions = -1;
|
|
offset_CR = -1;
|
|
offset_LF = -1;
|
|
for (i = self->buf_read_raw_offset, c = self->buf_read_raw + self->buf_read_raw_offset;
|
|
i < self->buf_read_raw_len;
|
|
i++, c++) {
|
|
if (in_quote) {
|
|
if (*c == '\\') in_quote = 2;
|
|
else if (*c == '"') in_quote--;
|
|
continue;
|
|
}
|
|
if (*c == '"') {
|
|
in_quote = 1;
|
|
} else if (*c == ';' && offset_extentions == -1) {
|
|
offset_extentions = i;
|
|
} else if (*c == '\r') {
|
|
offset_CR = i;
|
|
} else if (*c == '\n' && offset_CR == (ssize_t)(i - 1)) {
|
|
offset_LF = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ok, now we know a lot more!:
|
|
* offset_extentions is the offset to the extentions if any.
|
|
* offset_LF is the offset to the byte before the body if any.
|
|
* if offset_LF is > -1 we know we have a complet header.
|
|
* if we don't just return a short read and try again later.
|
|
*/
|
|
|
|
if (offset_LF == -1)
|
|
return 0;
|
|
|
|
/* ok. Now we have a complet header.
|
|
* First pass the extentions to extention parser if any.
|
|
*/
|
|
if (offset_extentions != -1)
|
|
__enc_chunked_read_extentions(self, self->buf_read_raw + offset_extentions, offset_CR - offset_extentions);
|
|
|
|
/* ok. Next we parse the body length.
|
|
* We just replace whatever comes after the length by \0
|
|
* and try to parse the hex value.
|
|
*/
|
|
if (offset_extentions != -1) {
|
|
c = self->buf_read_raw + offset_extentions;
|
|
} else {
|
|
c = self->buf_read_raw + offset_CR;
|
|
}
|
|
*c = 0;
|
|
|
|
/* we hope that will work... */
|
|
if (sscanf(self->buf_read_raw + self->buf_read_raw_offset, "%llx", &bodylen) != 1)
|
|
return -1;
|
|
|
|
/* ok, Now we move the offset forward to the body. */
|
|
self->buf_read_raw_offset = offset_LF + 1;
|
|
|
|
if (!bodylen) {
|
|
self->bytes_till_eof = 2; /* 2 = tailing "\r\n" */
|
|
}
|
|
|
|
/* Do we still have some data in buffer?
|
|
* If not free the buffer and set the counters
|
|
* to point to the next header.
|
|
*/
|
|
if (self->buf_read_raw_offset == self->buf_read_raw_len) {
|
|
free(self->buf_read_raw);
|
|
self->buf_read_raw = NULL;
|
|
self->buf_read_raw_offset = 0;
|
|
self->buf_read_raw_len = 0;
|
|
self->read_bytes_till_header = bodylen + 2; /* 2 = tailing "\r\n" */
|
|
return 0;
|
|
}
|
|
|
|
/* ok, now we check if what we have in the buffer is less or equal than our bodylen. */
|
|
if ((self->buf_read_raw_len - self->buf_read_raw_offset) <= bodylen) {
|
|
/* ok, this is fantastic. The framework can do the rest for us! */
|
|
self->buf_read_decoded = self->buf_read_raw;
|
|
self->buf_read_decoded_offset = self->buf_read_raw_offset;
|
|
self->buf_read_decoded_len = self->buf_read_raw_len;
|
|
self->buf_read_raw = NULL;
|
|
self->buf_read_raw_offset = 0;
|
|
self->buf_read_raw_len = 0;
|
|
self->read_bytes_till_header = bodylen + 2 - (self->buf_read_decoded_len - self->buf_read_decoded_offset);
|
|
return 0;
|
|
}
|
|
|
|
/* ok, final case left:
|
|
* we have more than just the body in our buffer.
|
|
* What we do now is to allocate buffer, move the body over
|
|
* and let our internal structures point to the next header.
|
|
*/
|
|
|
|
self->buf_read_decoded = malloc(bodylen);
|
|
if (!self->buf_read_decoded) /* just retry later if we can not allocate a buffer */
|
|
return -1;
|
|
self->buf_read_decoded_offset = 0;
|
|
self->buf_read_decoded_len = bodylen;
|
|
memcpy(self->buf_read_decoded, self->buf_read_raw + self->buf_read_raw_offset, bodylen);
|
|
self->buf_read_raw_offset += bodylen;
|
|
self->read_bytes_till_header = 2; /* tailing "\r\n" */
|
|
|
|
if ((self->buf_read_raw_len - self->buf_read_raw_offset) >= 2) {
|
|
self->buf_read_raw_offset += 2;
|
|
self->read_bytes_till_header = 0;
|
|
if (self->bytes_till_eof != -1)
|
|
self->bytes_till_eof = 0;
|
|
|
|
if ((self->buf_read_raw_len - self->buf_read_raw_offset) == 0) {
|
|
free(self->buf_read_raw);
|
|
self->buf_read_raw = NULL;
|
|
self->buf_read_raw_len = 0;
|
|
self->buf_read_raw_offset = 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t __enc_chunked_write_extensions_valuelen(httpp_meta_t *cur)
|
|
{
|
|
size_t ret = cur->value_len;
|
|
size_t i;
|
|
char *p = cur->value;
|
|
|
|
if (!cur->value || !cur->value_len)
|
|
return 0;
|
|
|
|
for (i = 0; i < cur->value_len; i++, p++)
|
|
if (*p == '"')
|
|
ret++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *__enc_chunked_write_extensions(igloo_httpp_encoding_t *self)
|
|
{
|
|
size_t buflen;
|
|
void *buf;
|
|
char *p;
|
|
size_t len;
|
|
httpp_meta_t *cur;
|
|
|
|
if (!self->meta_write)
|
|
return NULL;
|
|
|
|
/* first find out how long the buffer must be. */
|
|
buflen = 1; /* tailing \0 */
|
|
|
|
cur = self->meta_write;
|
|
while (cur) {
|
|
if (!cur->key || (cur->value_len && !cur->value)) {
|
|
cur = cur->next;
|
|
continue;
|
|
}
|
|
|
|
buflen += 4; /* ; and = and two " */
|
|
buflen += strlen(cur->key);
|
|
buflen += __enc_chunked_write_extensions_valuelen(cur);
|
|
cur = cur->next;
|
|
}
|
|
|
|
p = buf = malloc(buflen);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
cur = self->meta_write;
|
|
while (cur) {
|
|
if (!cur->key || (cur->value_len && !cur->value)) {
|
|
cur = cur->next;
|
|
continue;
|
|
}
|
|
|
|
*(p++) = ';';
|
|
len = strlen(cur->key);
|
|
memcpy(p, cur->key, len);
|
|
p += len;
|
|
|
|
if (cur->value_len) {
|
|
const char *c;
|
|
size_t i;
|
|
|
|
*(p++) = '=';
|
|
*(p++) = '"';
|
|
for (i = 0, c = cur->value; i < cur->value_len; i++, c++) {
|
|
if (*c == '"')
|
|
*(p++) = '\\';
|
|
*(p++) = *c;
|
|
}
|
|
*(p++) = '"';
|
|
}
|
|
|
|
cur = cur->next;
|
|
}
|
|
|
|
*p = 0; /* terminate the string */
|
|
|
|
igloo_httpp_encoding_meta_free(self->meta_write);
|
|
self->meta_write = NULL;
|
|
|
|
return buf;
|
|
}
|
|
static ssize_t igloo___enc_chunked_write(igloo_httpp_encoding_t *self, const void *buf, size_t len, ssize_t (*cb)(void*, const void*, size_t), void *userdata)
|
|
{
|
|
char encoded_length[32];
|
|
char *extensions = NULL;
|
|
ssize_t total_chunk_size;
|
|
ssize_t header_length;
|
|
|
|
(void)cb, (void)userdata;
|
|
|
|
if (!buf)
|
|
len = 0;
|
|
|
|
/* refuse to write if we still have stuff to flush. */
|
|
if (igloo_httpp_encoding_pending(self) > 0)
|
|
return 0;
|
|
|
|
/* limit length to a bit more sane value */
|
|
if (len > 1048576)
|
|
len = 1048576;
|
|
|
|
snprintf(encoded_length, sizeof(encoded_length), "%lx", (long int)len);
|
|
|
|
extensions = __enc_chunked_write_extensions(self);
|
|
|
|
/* 2 = end of header and tailing "\r\n" */
|
|
header_length = strlen(encoded_length) + (extensions ? strlen(extensions) : 0) + 2;
|
|
total_chunk_size = header_length + len + 2;
|
|
if (!buf)
|
|
total_chunk_size += 2;
|
|
|
|
/* ok, we now allocate a huge buffer. We do it as if we would do it only when needed
|
|
* and it would fail we would end in bad state that can not be recovered */
|
|
self->buf_write_encoded = malloc(total_chunk_size);
|
|
if (!self->buf_write_encoded) {
|
|
if (extensions)
|
|
free(extensions);
|
|
return -1;
|
|
}
|
|
|
|
self->buf_write_encoded_offset = 0;
|
|
self->buf_write_encoded_len = total_chunk_size;
|
|
snprintf(self->buf_write_encoded, total_chunk_size, "%s%s\r\n", encoded_length, extensions ? extensions : "");
|
|
memcpy(self->buf_write_encoded + header_length, buf, len);
|
|
memcpy(self->buf_write_encoded + header_length + len, "\r\n\r\n", buf ? 2 : 4);
|
|
|
|
if (extensions)
|
|
free(extensions);
|
|
|
|
return len;
|
|
}
|