bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "lib.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "istream-private.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "ostream-private.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "http-parser.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "http-header-parser.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "http-transfer.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen#define MIN_CHUNK_SIZE_WITH_EXTRA 6
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch/*
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch * Chunked input stream
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschenum http_transfer_chunked_parse_state {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_INIT,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_SIZE,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT_NAME,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT_EQ,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT_VALUE,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_ESCAPE,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_CR,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_LF,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_DATA,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_DATA_READY,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_DATA_CR,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_DATA_LF,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_TRAILER,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch HTTP_CHUNKED_PARSE_STATE_FINISHED,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch};
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstruct http_transfer_chunked_istream {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct istream_private istream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct stat statbuf;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const unsigned char *begin, *cur, *end;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch enum http_transfer_chunked_parse_state state;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch unsigned int parsed_chars;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch uoff_t chunk_size, chunk_v_offset, chunk_pos;
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch uoff_t size, max_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_header_parser *header_parser;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool finished:1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch};
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch/* Chunk parser */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic inline const char *_chr_sanitize(unsigned char c)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (c >= 0x20 && c < 0x7F)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return t_strdup_printf("'%c'", c);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return t_strdup_printf("0x%02x", c);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_transfer_chunked_parse_size
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch(struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch uoff_t size = 0, prev;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* chunk-size = 1*HEXDIG */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch while (tcstream->cur < tcstream->end) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch prev = tcstream->chunk_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur >= '0' && *tcstream->cur <= '9')
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size = *tcstream->cur-'0';
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch else if (*tcstream->cur >= 'A' && *tcstream->cur <= 'F')
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size = *tcstream->cur-'A' + 10;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch else if (*tcstream->cur >= 'a' && *tcstream->cur <= 'f')
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size = *tcstream->cur-'a' + 10;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch else {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->parsed_chars == 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch "Expected chunk size digit, but found %s",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch _chr_sanitize(*tcstream->cur));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->parsed_chars = 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->chunk_size <<= 4;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->chunk_size += size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->chunk_size < prev) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Chunk size exceeds integer limit");
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->parsed_chars++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_transfer_chunked_skip_token
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch(struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const unsigned char *first = tcstream->cur;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* token = 1*tchar */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch while (tcstream->cur < tcstream->end && http_char_is_token(*tcstream->cur))
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->parsed_chars += (tcstream->cur-first);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->cur == tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->parsed_chars == 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_transfer_chunked_skip_qdtext
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch(struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch /* qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch while (tcstream->cur < tcstream->end && http_char_is_qdtext(*tcstream->cur))
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->cur == tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_transfer_chunked_parse(struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch /* RFC 7230, Section 4.1: Chunked Transfer Encoding
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunked-body = *chunk
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch last-chunk
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch trailer-part
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch CRLF
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk = chunk-size [ chunk-ext ] CRLF
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-data CRLF
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-size = 1*HEXDIG
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch last-chunk = 1*("0") [ chunk-ext ] CRLF
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-ext-name = token
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch chunk-ext-val = token / quoted-string
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-data = 1*OCTET ; a sequence of chunk-size octets
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch trailer-part = *( header-field CRLF )
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch for (;;) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch switch (tcstream->state) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_INIT:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->chunk_size = 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->chunk_pos = 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->parsed_chars = 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_SIZE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_SIZE:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ((ret=http_transfer_chunked_parse_size(tcstream)) <= 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur != ';') {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_CR;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* chunk-ext */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_NAME;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->cur >= tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT_NAME:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* chunk-ext-name = token */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ((ret=http_transfer_chunked_skip_token(tcstream)) <= 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen if (ret < 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Invalid chunked extension name");
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_EQ;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT_EQ:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur != '=') {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->cur >= tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE:
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch /* chunk-ext-val = token / quoted-string */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur != '"') {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->cur >= tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING:
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen if (*tcstream->cur == '"') {
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen tcstream->cur++;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen if (tcstream->cur >= tcstream->end)
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen return 0;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen } else if ((ret=http_transfer_chunked_skip_qdtext(tcstream)) <= 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen if (ret < 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Invalid chunked extension value");
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen }
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen return ret;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen } else if (*tcstream->cur == '\\') {
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen tcstream->cur++;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_ESCAPE;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen if (tcstream->cur >= tcstream->end)
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen return 0;
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen } else {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen "Invalid character %s in chunked extension value string",
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen _chr_sanitize(*tcstream->cur));
59bc0bc0c0383d522cd1289c5847d6eac673121aTimo Sirainen return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_ESCAPE:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* ( HTAB / SP / VCHAR / obs-text ) */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (!http_char_is_text(*tcstream->cur)) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch "Escaped invalid character %s in chunked extension value string",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch _chr_sanitize(*tcstream->cur));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_STRING;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if (tcstream->cur >= tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_EXT_VALUE_TOKEN:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ((ret=http_transfer_chunked_skip_token(tcstream)) <= 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen if (ret < 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Invalid chunked extension value");
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_EXT;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_CR:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_LF;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur == '\r') {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if (tcstream->cur >= tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_LF:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur != '\n') {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch "Expected new line after chunk size, but found %s",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch _chr_sanitize(*tcstream->cur));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->chunk_size > 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch else
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_TRAILER;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_DATA_READY:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_DATA_CR:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA_LF;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur == '\r') {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if (tcstream->cur >= tcstream->end)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* fall through */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_DATA_LF:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (*tcstream->cur != '\n') {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch "Expected new line after chunk data, but found %s",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch _chr_sanitize(*tcstream->cur));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur++;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_INIT;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch default:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_unreached();
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_unreached();
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_transfer_chunked_parse_next(
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct istream_private *stream = &tcstream->istream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct istream *input = tcstream->istream.parent;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size_t size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
9184983183ae28fb543695c54c85bc5396c07e42Phil Carmody while ((ret=i_stream_read_more(input, &tcstream->begin, &size)) > 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->cur = tcstream->begin;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->end = tcstream->cur + size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if ((ret=http_transfer_chunked_parse(tcstream)) < 0) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch stream->istream.stream_errno = EIO;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_stream_skip(input, tcstream->cur - tcstream->begin);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (ret > 0) {
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch if (tcstream->state == HTTP_CHUNKED_PARSE_STATE_DATA) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->chunk_v_offset = input->v_offset;
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch tcstream->size += tcstream->chunk_size;
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch if (tcstream->max_size > 0 && tcstream->size > tcstream->max_size) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Total chunked payload size exceeds maximum");
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch stream->istream.stream_errno = EMSGSIZE;
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch return -1;
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch }
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(ret != -2);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if (ret < 0) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if ( stream->parent->eof && stream->parent->stream_errno == 0 ) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* unexpected EOF */
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Unexpected end of payload");
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch stream->istream.stream_errno = EIO;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch } else {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* parent stream error */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch stream->istream.stream_errno = stream->parent->stream_errno;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch/* Input stream */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic ssize_t
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_transfer_chunked_istream_read_data(
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct istream_private *stream = &tcstream->istream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const unsigned char *data;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size_t size, avail;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch ssize_t ret = 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->chunk_pos >= tcstream->chunk_size) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA_READY;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch // FIXME: is this even necessary?
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_stream_seek(stream->parent, tcstream->chunk_v_offset + tcstream->chunk_pos);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* read from parent if necessary */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch data = i_stream_get_data(stream->parent, &size);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (size == 0) {
bcd286622779a93f809b11993db0550f8c7cc9b5Timo Sirainen ret = i_stream_read_memarea(stream->parent);
22ee6e1e6193299034ab99f77a650290de2fd6caTimo Sirainen if (ret <= 0) {
22ee6e1e6193299034ab99f77a650290de2fd6caTimo Sirainen i_assert(ret != -2); /* 0 sized buffer can't be full */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ( stream->parent->eof && stream->parent->stream_errno == 0 ) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* unexpected EOF */
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&tcstream->istream.iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Unexpected end of payload");
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch stream->istream.stream_errno = EIO;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch } else {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* parent stream error */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch stream->istream.stream_errno = stream->parent->stream_errno;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch data = i_stream_get_data(stream->parent, &size);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(size != 0);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch size = size > (tcstream->chunk_size - tcstream->chunk_pos) ?
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch (tcstream->chunk_size - tcstream->chunk_pos) : size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* Allocate buffer space */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (!i_stream_try_alloc(stream, size, &avail))
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -2;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* Copy payload */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size = size > avail ? avail : size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch memcpy(&stream->w_buffer[stream->pos], data, size);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_stream_skip(stream->parent, size);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->chunk_pos += size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->chunk_pos >= tcstream->chunk_size)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_DATA_READY;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch ret = size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch stream->pos = stream->pos+size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_transfer_chunked_parse_trailer(
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_transfer_chunked_istream *tcstream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct istream_private *stream = &tcstream->istream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const char *field_name, *error;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const unsigned char *field_data;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size_t field_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch int ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->header_parser == NULL) {
e0cf44fb802a5e7aa4cbeee5e80ef8f3f6aecdbeStephan Bosch /* NOTE: trailer is currently ignored */
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch /* FIXME: limit trailer size */
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch tcstream->header_parser =
7ebcb054e0d3cc4be54038cbf763ec4189d9725bStephan Bosch http_header_parser_init(tcstream->istream.parent, NULL, 0);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch while ((ret=http_header_parse_next_field(tcstream->header_parser,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch &field_name, &field_data, &field_size, &error)) > 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (field_name == NULL) break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (ret <= 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (ret < 0) {
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen io_stream_set_error(&stream->iostream,
ac3d5a11ff86b23eb2e416a7fe9406cef271930dTimo Sirainen "Failed to parse chunked trailer: %s", error);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch stream->istream.stream_errno = EIO;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic ssize_t
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_transfer_chunked_istream_read(struct istream_private *stream)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_transfer_chunked_istream *tcstream =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch (struct http_transfer_chunked_istream *)stream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch ssize_t ret = 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch for (;;) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch switch (tcstream->state) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_FINISHED:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.istream.eof = TRUE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_DATA:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ((ret=http_transfer_chunked_istream_read_data(tcstream)) != 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (tcstream->state != HTTP_CHUNKED_PARSE_STATE_DATA_READY)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return 0;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch break;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch case HTTP_CHUNKED_PARSE_STATE_TRAILER:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ((ret=http_transfer_chunked_parse_trailer(tcstream)) <= 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->state = HTTP_CHUNKED_PARSE_STATE_FINISHED;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.istream.eof = TRUE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch default:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if ((ret=http_transfer_chunked_parse_next(tcstream)) <= 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return -1;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainenstatic void
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainenhttp_transfer_chunked_istream_destroy(struct iostream_private *stream)
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen{
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen struct http_transfer_chunked_istream *tcstream =
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen (struct http_transfer_chunked_istream *)stream;
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (tcstream->header_parser != NULL)
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen http_header_parser_deinit(&tcstream->header_parser);
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen // FIXME: copied from istream.c; there's got to be a better way.
54bd0fec0be357266e299466a582f3c9269884e9Timo Sirainen i_stream_free_buffer(&tcstream->istream);
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen}
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstruct istream *
1175415b88ff168e367c77df23901eada13225b9Stephan Boschhttp_transfer_chunked_istream_create(struct istream *input, uoff_t max_size)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_transfer_chunked_istream *tcstream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream = i_new(struct http_transfer_chunked_istream, 1);
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch tcstream->max_size = max_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.max_buffer_size =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch input->real_stream->max_buffer_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen tcstream->istream.iostream.destroy = http_transfer_chunked_istream_destroy;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.read = http_transfer_chunked_istream_read;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.istream.readable_fd = FALSE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.istream.blocking = input->blocking;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch tcstream->istream.istream.seekable = FALSE;
2974dca6be5120e49279f06c8aa952e5fac56048Timo Sirainen return i_stream_create(&tcstream->istream, input, i_stream_get_fd(input), 0);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch/*
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch * Chunked output stream
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch// FIXME: provide support for corking the stream. This means that we'll have
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch// to buffer sent data here rather than in the parent steam; we need to know
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch// the size of the chunks before we can send them.
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch#define DEFAULT_MAX_BUFFER_SIZE (1024*32)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschstruct http_transfer_chunked_ostream {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct ostream_private ostream;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch size_t chunk_size, chunk_pos;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool chunk_active:1;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch};
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschstatic size_t _log16(size_t in)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch{
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch size_t res = 0;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch while (in > 0) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch in >>= 4;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch res++;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch return res;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch}
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschstatic size_t _max_chunk_size(size_t avail)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch{
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen size_t chunk_extra = 2*2;
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* Make sure we have room for both chunk data and overhead
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch chunk = chunk-size [ chunk-ext ] CRLF
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-data CRLF
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch chunk-size = 1*HEXDIG
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch */
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen chunk_extra += _log16(avail);
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen return avail < chunk_extra ? 0 :
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen avail - chunk_extra;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch}
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschstatic void
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainenhttp_transfer_chunked_ostream_close(struct iostream_private *stream,
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen bool close_parent)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch{
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct http_transfer_chunked_ostream *tcstream =
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch (struct http_transfer_chunked_ostream *)stream;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch (void)o_stream_send(tcstream->ostream.parent, "0\r\n\r\n", 5);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch (void)o_stream_flush(&tcstream->ostream.ostream);
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen if (close_parent)
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen o_stream_close(tcstream->ostream.parent);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch}
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschstatic ssize_t
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschhttp_transfer_chunked_ostream_sendv(struct ostream_private *stream,
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch const struct const_iovec *iov, unsigned int iov_count)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch{
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct http_transfer_chunked_ostream *tcstream =
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch (struct http_transfer_chunked_ostream *)stream;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct const_iovec *iov_new;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch unsigned int iov_count_new, i;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch size_t bytes = 0, max_bytes;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch ssize_t ret;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch const char *prefix;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen i_assert(stream->parent->real_stream->max_buffer_size >= MIN_CHUNK_SIZE_WITH_EXTRA);
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if ((ret=o_stream_flush(stream->parent)) <= 0) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* error / we still couldn't flush existing data to
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch parent stream. */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch o_stream_copy_error_from_parent(stream);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch return ret;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* check how many bytes we want to send */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch bytes = 0;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch for (i = 0; i < iov_count; i++) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch bytes += iov[i].iov_len;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* check if we have room to send at least one byte */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch max_bytes = o_stream_get_buffer_avail_size(stream->parent);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch max_bytes = _max_chunk_size(max_bytes);
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen if (max_bytes < MIN_CHUNK_SIZE_WITH_EXTRA)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch return 0;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch tcstream->chunk_size = bytes > max_bytes ? max_bytes : bytes;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* determine what to send */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch bytes = tcstream->chunk_size;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_count_new = 1;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch for (i = 0; i < iov_count && bytes > 0; i++) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if (bytes <= iov[i].iov_len)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch break;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch bytes -= iov[i].iov_len;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_count_new++;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* create new iovec */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch prefix = t_strdup_printf("%llx\r\n", (unsigned long long)tcstream->chunk_size);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_count = iov_count_new + 2;
657d704a513d74c8bd0b2d355c88d8723a4ec69bMartti Rannanjärvi iov_new = t_new(struct const_iovec, iov_count);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_new[0].iov_base = prefix;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_new[0].iov_len = strlen(prefix);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch memcpy(&iov_new[1], iov, sizeof(struct const_iovec) * iov_count_new);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_new[iov_count-2].iov_len = bytes;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_new[iov_count-1].iov_base = "\r\n";
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_new[iov_count-1].iov_len = 2;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* send */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if ((ret=o_stream_sendv(stream->parent, iov_new, iov_count)) <= 0) {
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch i_assert(ret < 0);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch o_stream_copy_error_from_parent(stream);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch return -1;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch }
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* all must be sent */
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch i_assert((size_t)ret == (tcstream->chunk_size +
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch iov_new[0].iov_len + iov_new[iov_count-1].iov_len));
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch stream->ostream.offset += tcstream->chunk_size;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch return tcstream->chunk_size;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch}
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschstruct ostream *
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Boschhttp_transfer_chunked_ostream_create(struct ostream *output)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch{
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch struct http_transfer_chunked_ostream *tcstream;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch size_t max_size;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch tcstream = i_new(struct http_transfer_chunked_ostream, 1);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch tcstream->ostream.sendv = http_transfer_chunked_ostream_sendv;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch tcstream->ostream.iostream.close = http_transfer_chunked_ostream_close;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch if (output->real_stream->max_buffer_size > 0)
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch max_size = output->real_stream->max_buffer_size;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch else
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch max_size = DEFAULT_MAX_BUFFER_SIZE;
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch tcstream->ostream.max_buffer_size = _max_chunk_size(max_size);
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch return o_stream_create(&tcstream->ostream, output,
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch o_stream_get_fd(output));
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch}