
#include <lighttpd/base.h>

/* len != 0 */
static void http_chunk_append_len(liChunkQueue *cq, size_t len) {
	size_t i, olen = len, j;
	GByteArray *a;

	a = g_byte_array_sized_new(sizeof(len) * 2 + 2);

	for (i = 0; i < 8 && len; i++) {
		len >>= 4;
	}

	/* i is the number of hex digits we have */
	g_byte_array_set_size(a, i);

	for (j = i, len = olen; j-- > 0; ) {
		a->data[j] = (len & 0xf) + (((len & 0xf) <= 9) ? '0' : 'a' - 10);
		len >>= 4;
	}
	g_byte_array_append(a, CONST_USTR_LEN("\r\n"));

	li_chunkqueue_append_bytearr(cq, a);
}


liHandlerResult li_filter_chunked_encode(liVRequest *vr, liChunkQueue *out, liChunkQueue *in) {
	UNUSED(vr);

	if (in->length > 0) {
		http_chunk_append_len(out, in->length);
		li_chunkqueue_steal_all(out, in);
		li_chunkqueue_append_mem(out, CONST_STR_LEN("\r\n"));
	}
	if (in->is_closed) {
		if (!out->is_closed) {
			li_chunkqueue_append_mem(out, CONST_STR_LEN("0\r\n\r\n"));
			out->is_closed = TRUE;
		}
		return LI_HANDLER_GO_ON;
	}
	return LI_HANDLER_GO_ON;
}

#define read_char(c) do { \
	while (!p || p >= pe) { \
		GError *err = NULL; \
		liHandlerResult res = li_chunk_parser_next(&ctx, &p, &pe, &err); \
		if (NULL != err) { \
			VR_ERROR(vr, "%s", err->message); \
			g_error_free(err); \
		} \
		if (LI_HANDLER_ERROR == res || (LI_HANDLER_WAIT_FOR_EVENT == res && in->is_closed)) { \
			goto error; \
		} \
		if (res != LI_HANDLER_GO_ON) goto leave; \
	} \
	c = *p++; \
} while(0);


gboolean li_filter_chunked_decode(liVRequest *vr, liChunkQueue *out, liChunkQueue *in, liFilterChunkedDecodeState *state) {
	liChunkParserCtx ctx;
	gchar *p = NULL, *pe = NULL;
	gchar c;
	int digit;

	li_chunk_parser_init(&ctx, in);
	li_chunk_parser_prepare(&ctx);

	for (;;) {
		 /* 0: start new chunklen, 1: reading chunklen, 2: found \r, 3: copying content, 4: found \r,
		  * 10: wait for \r\n\r\n, 11: wait for \n\r\n, 12: wait for \r\n, 13: wait for \n, 14: eof,
		  * 20: error
		  */
		switch (state->parse_state) {
		case 0:
			state->cur_chunklen = -1;
			state->parse_state = 1;
			break;
		case 1:
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			digit = -1;
			if (c >= '0' && c <= '9') {
				digit = c - '0';
			} else if (c >= 'a' && c <= 'f') {
				digit = c - 'a' + 10;
			} else if (c >= 'A' && c <= 'F') {
				digit = c - 'A' + 10;
			} else if (c == '\r') {
				if (state->cur_chunklen == -1) {
					state->parse_state = 20;
				} else {
					state->parse_state = 2;
				}
			} else {
				state->parse_state = 20;
			}
			if (digit >= 0) {
				if (state->cur_chunklen < 0) {
					state->cur_chunklen = digit;
				} else {
					if ((G_MAXINT64 - digit) / 16 < state->cur_chunklen) {
						state->parse_state = 20; /* overflow */
					} else {
						state->cur_chunklen = 16 * state->cur_chunklen + digit;
					}
				}
			}
			break;
		case 2:
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			if (c == '\n') {
				li_chunkqueue_skip(in, ctx.bytes_in);
				if (state->cur_chunklen > 0) {
					state->parse_state = 3;
				} else {
					li_chunk_parser_reset(&ctx); p = NULL;
					li_chunk_parser_prepare(&ctx);
					state->parse_state = 12;
				}
			} else {
				state->parse_state = 20;
			}
			break;
		case 3:
			if (state->cur_chunklen != 0) {
				state->cur_chunklen -= li_chunkqueue_steal_len(out, in, state->cur_chunklen);
			}
			if (state->cur_chunklen == 0) {
				li_chunk_parser_reset(&ctx); p = NULL;
				li_chunk_parser_prepare(&ctx);
				read_char(c);
				li_chunk_parser_done(&ctx, 1);
				if (c == '\r') {
					state->parse_state = 4;
				} else {
					state->parse_state = 20;
				}
			} else {
				/* wait for more data for the current chunk */
				return TRUE;
			}
			break;
		case 4:
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			if (c == '\n') {
				li_chunkqueue_skip(in, ctx.bytes_in);
				li_chunk_parser_reset(&ctx); p = NULL;
				li_chunk_parser_prepare(&ctx);
				state->parse_state = 0;
			} else {
				state->parse_state = 20;
			}
			break;
		case 10: /* \r\n\r\n */
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			state->parse_state = (c == '\r') ? 11 : 10;
			break;
		case 11: /* \n\r\n */
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			state->parse_state = (c == '\n') ? 12 : 10;
			break;
		case 12: /* \r\n */
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			state->parse_state = (c == '\r') ? 13 : 10;
			break;
		case 13: /* \n */
			read_char(c);
			li_chunk_parser_done(&ctx, 1);
			state->parse_state = (c == '\n') ? 14 : 10;
			break;
		case 14:
			out->is_closed = TRUE;
			goto leave;
		case 20:
			goto error;
		}
	}

leave:
	li_chunkqueue_skip(in, ctx.bytes_in);
	return TRUE;

error:
	out->is_closed = TRUE;
	li_chunkqueue_skip_all(in);
	state->parse_state = 20;
	return FALSE;
}
