308 lines
7.7 KiB
C
308 lines
7.7 KiB
C
|
|
/*
|
|
* Copyright (C) Igor Sysoev
|
|
* Copyright (C) NGINX, Inc.
|
|
*/
|
|
|
|
#include <nxt_main.h>
|
|
|
|
|
|
#define NXT_FASTCGI_DATA_MIDDLE 0
|
|
#define NXT_FASTCGI_DATA_END_ON_BORDER 1
|
|
#define NXT_FASTCGI_DATA_END 2
|
|
|
|
|
|
static nxt_int_t nxt_fastcgi_buffer(nxt_fastcgi_parse_t *fp, nxt_buf_t ***tail,
|
|
nxt_buf_t *in);
|
|
|
|
|
|
void
|
|
nxt_fastcgi_record_parse(nxt_task_t *task, nxt_fastcgi_parse_t *fp,
|
|
nxt_buf_t *in)
|
|
{
|
|
u_char ch;
|
|
nxt_int_t ret, stream;
|
|
nxt_buf_t *b, *nb, **tail[2];
|
|
const char *msg;
|
|
enum {
|
|
sw_fastcgi_version = 0,
|
|
sw_fastcgi_type,
|
|
sw_fastcgi_request_id_high,
|
|
sw_fastcgi_request_id_low,
|
|
sw_fastcgi_content_length_high,
|
|
sw_fastcgi_content_length_low,
|
|
sw_fastcgi_padding_length,
|
|
sw_fastcgi_reserved,
|
|
sw_fastcgi_data,
|
|
sw_fastcgi_padding,
|
|
sw_fastcgi_end_request,
|
|
} state;
|
|
|
|
fp->out[0] = NULL;
|
|
fp->out[1] = NULL;
|
|
|
|
tail[0] = &fp->out[0];
|
|
tail[1] = &fp->out[1];
|
|
|
|
state = fp->state;
|
|
|
|
for (b = in; b != NULL; b = b->next) {
|
|
|
|
if (nxt_buf_is_sync(b)) {
|
|
**tail = b;
|
|
*tail = &b->next;
|
|
continue;
|
|
}
|
|
|
|
fp->pos = b->mem.pos;
|
|
|
|
while (fp->pos < b->mem.free) {
|
|
/*
|
|
* The sw_fastcgi_data state is tested outside the
|
|
* switch to preserve fp->pos and to not touch memory.
|
|
*/
|
|
if (state == sw_fastcgi_data) {
|
|
|
|
/*
|
|
* fp->type here can be only NXT_FASTCGI_STDOUT
|
|
* or NXT_FASTCGI_STDERR. NXT_FASTCGI_END_REQUEST
|
|
* is tested in sw_fastcgi_reserved.
|
|
*/
|
|
stream = fp->type - NXT_FASTCGI_STDOUT;
|
|
|
|
ret = nxt_fastcgi_buffer(fp, &tail[stream], b);
|
|
|
|
if (ret == NXT_FASTCGI_DATA_MIDDLE) {
|
|
goto next;
|
|
}
|
|
|
|
if (nxt_slow_path(ret == NXT_ERROR)) {
|
|
fp->error = 1;
|
|
goto done;
|
|
}
|
|
|
|
if (fp->padding == 0) {
|
|
state = sw_fastcgi_version;
|
|
|
|
} else {
|
|
state = sw_fastcgi_padding;
|
|
}
|
|
|
|
if (ret == NXT_FASTCGI_DATA_END_ON_BORDER) {
|
|
goto next;
|
|
}
|
|
|
|
/* ret == NXT_FASTCGI_DATA_END */
|
|
}
|
|
|
|
ch = *fp->pos++;
|
|
|
|
nxt_thread_log_debug("fastcgi record byte: %02Xd", ch);
|
|
|
|
switch (state) {
|
|
|
|
case sw_fastcgi_version:
|
|
if (nxt_fast_path(ch == 1)) {
|
|
state = sw_fastcgi_type;
|
|
continue;
|
|
}
|
|
|
|
msg = "unsupported FastCGI protocol version";
|
|
goto fastcgi_error;
|
|
|
|
case sw_fastcgi_type:
|
|
switch (ch) {
|
|
case NXT_FASTCGI_STDOUT:
|
|
case NXT_FASTCGI_STDERR:
|
|
case NXT_FASTCGI_END_REQUEST:
|
|
fp->type = ch;
|
|
state = sw_fastcgi_request_id_high;
|
|
continue;
|
|
default:
|
|
msg = "invalid FastCGI record type";
|
|
goto fastcgi_error;
|
|
}
|
|
|
|
case sw_fastcgi_request_id_high:
|
|
/* FastCGI multiplexing is not supported. */
|
|
if (nxt_fast_path(ch == 0)) {
|
|
state = sw_fastcgi_request_id_low;
|
|
continue;
|
|
}
|
|
|
|
msg = "unexpected FastCGI request ID high byte";
|
|
goto fastcgi_error;
|
|
|
|
case sw_fastcgi_request_id_low:
|
|
if (nxt_fast_path(ch == 1)) {
|
|
state = sw_fastcgi_content_length_high;
|
|
continue;
|
|
}
|
|
|
|
msg = "unexpected FastCGI request ID low byte";
|
|
goto fastcgi_error;
|
|
|
|
case sw_fastcgi_content_length_high:
|
|
fp->length = ch << 8;
|
|
state = sw_fastcgi_content_length_low;
|
|
continue;
|
|
|
|
case sw_fastcgi_content_length_low:
|
|
fp->length |= ch;
|
|
state = sw_fastcgi_padding_length;
|
|
continue;
|
|
|
|
case sw_fastcgi_padding_length:
|
|
fp->padding = ch;
|
|
state = sw_fastcgi_reserved;
|
|
continue;
|
|
|
|
case sw_fastcgi_reserved:
|
|
nxt_thread_log_debug("fastcgi record type:%d "
|
|
"length:%uz padding:%d",
|
|
fp->type, fp->length, fp->padding);
|
|
|
|
if (nxt_fast_path(fp->type != NXT_FASTCGI_END_REQUEST)) {
|
|
state = sw_fastcgi_data;
|
|
continue;
|
|
}
|
|
|
|
state = sw_fastcgi_end_request;
|
|
continue;
|
|
|
|
case sw_fastcgi_data:
|
|
/*
|
|
* This state is processed before the switch.
|
|
* It added here just to suppress a warning.
|
|
*/
|
|
continue;
|
|
|
|
case sw_fastcgi_padding:
|
|
/*
|
|
* No special fast processing of padding
|
|
* because it usually takes just 1-7 bytes.
|
|
*/
|
|
fp->padding--;
|
|
|
|
if (fp->padding == 0) {
|
|
nxt_thread_log_debug("fastcgi record end");
|
|
state = sw_fastcgi_version;
|
|
}
|
|
continue;
|
|
|
|
case sw_fastcgi_end_request:
|
|
/* Just skip 8 bytes of END_REQUEST. */
|
|
fp->length--;
|
|
|
|
if (fp->length != 0) {
|
|
continue;
|
|
}
|
|
|
|
fp->done = 1;
|
|
|
|
nxt_thread_log_debug("fastcgi end request");
|
|
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (b->retain == 0) {
|
|
/* No record data was found in a buffer. */
|
|
nxt_thread_current_work_queue_add(task->thread,
|
|
b->completion_handler,
|
|
task, b, b->parent);
|
|
}
|
|
|
|
next:
|
|
|
|
continue;
|
|
}
|
|
|
|
fp->state = state;
|
|
|
|
return;
|
|
|
|
fastcgi_error:
|
|
|
|
nxt_thread_log_error(NXT_LOG_ERR, "upstream sent %s: %d", msg, ch);
|
|
|
|
fp->fastcgi_error = 1;
|
|
|
|
done:
|
|
|
|
nb = fp->last_buf(fp);
|
|
|
|
if (nxt_fast_path(nb != NULL)) {
|
|
*tail[0] = nb;
|
|
|
|
} else {
|
|
fp->error = 1;
|
|
}
|
|
|
|
// STUB: fp->fastcgi_error = 1;
|
|
// STUB: fp->error = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_fastcgi_buffer(nxt_fastcgi_parse_t *fp, nxt_buf_t ***tail, nxt_buf_t *in)
|
|
{
|
|
u_char *p;
|
|
size_t size;
|
|
nxt_buf_t *b;
|
|
|
|
if (fp->length == 0) {
|
|
return NXT_FASTCGI_DATA_END;
|
|
}
|
|
|
|
p = fp->pos;
|
|
size = in->mem.free - p;
|
|
|
|
if (fp->length >= size && in->retain == 0) {
|
|
/*
|
|
* Use original buffer if the buffer is lesser than or equal to
|
|
* FastCGI record size and this is the first record in the buffer.
|
|
*/
|
|
in->mem.pos = p;
|
|
**tail = in;
|
|
*tail = &in->next;
|
|
|
|
} else {
|
|
b = nxt_buf_mem_alloc(fp->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 (fp->length < size) {
|
|
p += fp->length;
|
|
fp->pos = p;
|
|
|
|
b->mem.free = p;
|
|
b->mem.end = p;
|
|
|
|
return NXT_FASTCGI_DATA_END;
|
|
}
|
|
|
|
b->mem.free = in->mem.free;
|
|
b->mem.end = in->mem.free;
|
|
}
|
|
|
|
fp->length -= size;
|
|
|
|
if (fp->length == 0) {
|
|
return NXT_FASTCGI_DATA_END_ON_BORDER;
|
|
}
|
|
|
|
return NXT_FASTCGI_DATA_MIDDLE;
|
|
}
|