/* * Copyright (C) Igor Sysoev * Copyright (C) NGINX, Inc. */ #include #define NXT_HTTP_CHUNK_MIDDLE 0 #define NXT_HTTP_CHUNK_END_ON_BORDER 1 #define NXT_HTTP_CHUNK_END 2 #define \ nxt_size_is_sufficient(cs) \ (cs < ((__typeof__(cs)) 1 << (sizeof(cs) * 8 - 4))) static nxt_int_t nxt_http_chunk_buffer(nxt_http_chunk_parse_t *hcp, nxt_buf_t ***tail, nxt_buf_t *in); nxt_buf_t * nxt_http_chunk_parse(nxt_http_chunk_parse_t *hcp, nxt_buf_t *in) { u_char c, ch; nxt_int_t ret; nxt_buf_t *b, *out, *nb, **tail; nxt_thread_t *thr; enum { sw_start = 0, sw_chunk_size, sw_chunk_size_linefeed, sw_chunk_end_newline, sw_chunk_end_linefeed, sw_chunk, } state; out = NULL; tail = &out; state = hcp->state; for (b = in; b != NULL; b = b->next) { hcp->pos = b->mem.pos; while (hcp->pos < b->mem.free) { /* * The sw_chunk state is tested outside the switch * to preserve hcp->pos and to not touch memory. */ if (state == sw_chunk) { ret = nxt_http_chunk_buffer(hcp, &tail, b); if (ret == NXT_HTTP_CHUNK_MIDDLE) { goto next; } if (nxt_slow_path(ret == NXT_ERROR)) { hcp->error = 1; goto done; } state = sw_chunk_end_newline; if (ret == NXT_HTTP_CHUNK_END_ON_BORDER) { goto next; } /* ret == NXT_HTTP_CHUNK_END_ON_BORDER */ } ch = *hcp->pos++; switch (state) { case sw_start: state = sw_chunk_size; c = ch - '0'; if (c <= 9) { hcp->chunk_size = c; continue; } c = (ch | 0x20) - 'a'; if (c <= 5) { hcp->chunk_size = 0x0a + c; continue; } goto chunk_error; case sw_chunk_size: c = ch - '0'; if (c > 9) { c = (ch | 0x20) - 'a'; if (nxt_fast_path(c <= 5)) { c += 0x0a; } else if (nxt_fast_path(ch == NXT_CR)) { state = sw_chunk_size_linefeed; continue; } else { goto chunk_error; } } if (nxt_fast_path(nxt_size_is_sufficient(hcp->chunk_size))) { hcp->chunk_size = (hcp->chunk_size << 4) + c; continue; } goto chunk_error; case sw_chunk_size_linefeed: if (nxt_fast_path(ch == NXT_LF)) { if (hcp->chunk_size != 0) { state = sw_chunk; continue; } hcp->last = 1; state = sw_chunk_end_newline; continue; } goto chunk_error; case sw_chunk_end_newline: if (nxt_fast_path(ch == NXT_CR)) { state = sw_chunk_end_linefeed; continue; } goto chunk_error; case sw_chunk_end_linefeed: if (nxt_fast_path(ch == NXT_LF)) { if (!hcp->last) { state = sw_start; continue; } goto done; } goto chunk_error; case sw_chunk: /* * This state is processed before the switch. * It added here just to suppress a warning. */ continue; } } if (b->retain == 0) { /* No chunk data was found in a buffer. */ thr = nxt_thread(); nxt_thread_current_work_queue_add(thr, b->completion_handler, b, b->parent, thr->log); } next: continue; } hcp->state = state; return out; chunk_error: hcp->chunk_error = 1; done: nb = nxt_buf_sync_alloc(hcp->mem_pool, NXT_BUF_SYNC_LAST); if (nxt_fast_path(nb != NULL)) { *tail = nb; } else { hcp->error = 1; } // STUB: hcp->chunk_error = 1; // STUB: hcp->error = 1; return out; } static nxt_int_t nxt_http_chunk_buffer(nxt_http_chunk_parse_t *hcp, nxt_buf_t ***tail, nxt_buf_t *in) { u_char *p; size_t size; nxt_buf_t *b; p = hcp->pos; size = in->mem.free - p; if (hcp->chunk_size >= size && in->retain == 0) { /* * Use original buffer if the buffer is lesser than or equal * to a chunk size and this is the first chunk in the buffer. */ in->mem.pos = p; **tail = in; *tail = &in->next; } else { b = nxt_buf_mem_alloc(hcp->mem_pool, 0, 0); if (nxt_slow_path(b == NULL)) { return NXT_ERROR; } **tail = b; *tail = &b->next; b->parent = in; in->retain++; b->mem.pos = p; b->mem.start = p; if (hcp->chunk_size < size) { p += hcp->chunk_size; hcp->pos = p; b->mem.free = p; b->mem.end = p; return NXT_HTTP_CHUNK_END; } b->mem.free = in->mem.free; b->mem.end = in->mem.free; } hcp->chunk_size -= size; if (hcp->chunk_size == 0) { return NXT_HTTP_CHUNK_END_ON_BORDER; } return NXT_HTTP_CHUNK_MIDDLE; }