static liHandlerResult cache_etag_filter_miss(liVRequest *vr, liFilter *f) { cache_etag_file *cfile = (cache_etag_file*) f->param; ssize_t res; gchar *buf; off_t buflen; liChunkIter citer = li_chunkqueue_iter(f->in); UNUSED(vr); if (0 == f->in->length) return LI_HANDLER_GO_ON; if (!cfile) { /* somehow we lost the file */ li_chunkqueue_steal_all(f->out, f->in); if (f->in->is_closed) f->out->is_closed = TRUE; return LI_HANDLER_GO_ON; } if (LI_HANDLER_GO_ON != li_chunkiter_read(vr, citer, 0, 64*1024, &buf, &buflen)) { VR_ERROR(vr, "%s", "Couldn't read data from chunkqueue"); cache_etag_file_free(cfile); f->param = NULL; li_chunkqueue_steal_all(f->out, f->in); if (f->in->is_closed) f->out->is_closed = TRUE; return LI_HANDLER_GO_ON; } res = write(cfile->fd, buf, buflen); if (res < 0) { switch (errno) { case EINTR: case EAGAIN: break; /* come back later */ default: VR_ERROR(vr, "Couldn't write to temporary cache file '%s': %s", cfile->tmpfilename->str, g_strerror(errno)); cache_etag_file_free(cfile); f->param = NULL; li_chunkqueue_steal_all(f->out, f->in); if (f->in->is_closed) f->out->is_closed = TRUE; return LI_HANDLER_GO_ON; } } else { li_chunkqueue_steal_len(f->out, f->in, res); if (f->in->length == 0 && f->in->is_closed) { cache_etag_file_finish(vr, cfile); f->param = NULL; f->out->is_closed = TRUE; return LI_HANDLER_GO_ON; } } return f->in->length ? LI_HANDLER_COMEBACK : LI_HANDLER_GO_ON; }
static void stream_send_chunks(liChunkQueue *out, guint8 type, guint16 requestid, liChunkQueue *in) { while (in->length > 0) { guint16 tosend = (in->length > G_MAXUINT16) ? G_MAXUINT16 : in->length; guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend); li_chunkqueue_steal_len(out, in, tosend); li_chunkqueue_append_mem(out, __padding, padlen); } if (in->is_closed && !out->is_closed) { out->is_closed = TRUE; stream_send_fcgi_record(out, type, requestid, 0); } }
static gboolean fastcgi_parse_response(fastcgi_connection *fcon) { liVRequest *vr = fcon->vr; liPlugin *p = fcon->ctx->plugin; gint len; while (fastcgi_get_packet(fcon)) { if (fcon->fcgi_in_record.version != FCGI_VERSION_1) { VR_ERROR(vr, "(%s) Unknown fastcgi protocol version %i", fcon->ctx->socket_str->str, (gint) fcon->fcgi_in_record.version); close(fcon->fd); fcon->fd = -1; li_vrequest_error(vr); return FALSE; } switch (fcon->fcgi_in_record.type) { case FCGI_END_REQUEST: li_chunkqueue_skip(fcon->fcgi_in, fastcgi_available(fcon)); fcon->stdout->is_closed = TRUE; break; case FCGI_STDOUT: if (0 == fcon->fcgi_in_record.contentLength) { fcon->stdout->is_closed = TRUE; } else { li_chunkqueue_steal_len(fcon->stdout, fcon->fcgi_in, fastcgi_available(fcon)); } break; case FCGI_STDERR: len = fastcgi_available(fcon); li_chunkqueue_extract_to(vr, fcon->fcgi_in, len, vr->wrk->tmp_str); if (OPTION(FASTCGI_OPTION_LOG_PLAIN_ERRORS).boolean) { li_log_split_lines(vr->wrk->srv, vr, LI_LOG_LEVEL_BACKEND, 0, vr->wrk->tmp_str->str, ""); } else { VR_BACKEND_LINES(vr, vr->wrk->tmp_str->str, "(fcgi-stderr %s) ", fcon->ctx->socket_str->str); } li_chunkqueue_skip(fcon->fcgi_in, len); break; default: if (fcon->fcgi_in_record.first) VR_WARNING(vr, "(%s) Unhandled fastcgi record type %i", fcon->ctx->socket_str->str, (gint) fcon->fcgi_in_record.type); li_chunkqueue_skip(fcon->fcgi_in, fastcgi_available(fcon)); break; } fcon->fcgi_in_record.first = FALSE; } return TRUE; }
/* tcp/ssl -> http "parser" */ static void _connection_http_in_cb(liStream *stream, liStreamEvent event) { liConnection *con = LI_CONTAINER_OF(stream, liConnection, in); liChunkQueue *raw_in, *in; liVRequest *vr = con->mainvr; switch (event) { case LI_STREAM_NEW_DATA: /* handle below */ break; case LI_STREAM_DISCONNECTED_SOURCE: connection_close(con); return; case LI_STREAM_DESTROY: con->info.req = NULL; li_job_later(&con->wrk->loop.jobqueue, &con->job_reset); return; default: return; } if (NULL == stream->source) return; /* raw_in never gets closed normally - if we receive EOF from the client it means it cancelled the request */ raw_in = stream->source->out; if (raw_in->is_closed) { connection_close(con); return; } /* always close "in" after request body end. reopen it on keep-alive */ in = con->in.out; if (0 == raw_in->length) return; /* no (new) data */ if (LI_CON_STATE_UPGRADED == con->state) { li_chunkqueue_steal_all(in, raw_in); li_stream_notify(stream); return; } if (con->state == LI_CON_STATE_KEEP_ALIVE) { /* stop keep alive timeout watchers */ if (con->keep_alive_data.link) { g_queue_delete_link(&con->wrk->keep_alive_queue, con->keep_alive_data.link); con->keep_alive_data.link = NULL; } con->keep_alive_data.timeout = 0; li_event_stop(&con->keep_alive_data.watcher); con->keep_alive_requests++; /* disable keep alive if limit is reached */ if (con->keep_alive_requests == CORE_OPTION(LI_CORE_OPTION_MAX_KEEP_ALIVE_REQUESTS).number) con->info.keep_alive = FALSE; /* reopen stream for request body */ li_chunkqueue_reset(in); /* reset stuff from keep-alive and record timestamp */ li_vrequest_start(con->mainvr); con->state = LI_CON_STATE_READ_REQUEST_HEADER; /* put back in io timeout queue */ li_connection_update_io_wait(con); } else if (con->state == LI_CON_STATE_REQUEST_START) { con->state = LI_CON_STATE_READ_REQUEST_HEADER; li_connection_update_io_wait(con); } if (con->state == LI_CON_STATE_READ_REQUEST_HEADER) { liHandlerResult res; if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "reading request header"); } res = li_http_request_parse(vr, &con->req_parser_ctx); /* max uri length 8 kilobytes */ /* TODO: check this and similar in request_parse and response_parse */ if (vr->request.uri.raw->len > 8*1024) { VR_INFO(vr, "request uri too large. limit: 8kb, received: %s", li_counter_format(vr->request.uri.raw->len, COUNTER_BYTES, vr->wrk->tmp_str)->str ); con->info.keep_alive = FALSE; vr->response.http_status = 414; /* Request-URI Too Large */ con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } switch(res) { case LI_HANDLER_GO_ON: break; /* go on */ case LI_HANDLER_WAIT_FOR_EVENT: return; case LI_HANDLER_ERROR: case LI_HANDLER_COMEBACK: /* unexpected */ /* unparsable header */ if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "parsing header failed"); } con->wrk->stats.requests++; con->info.keep_alive = FALSE; /* set status 400 if not already set to e.g. 413 */ if (vr->response.http_status == 0) vr->response.http_status = 400; con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } con->wrk->stats.requests++; /* headers ready */ if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "validating request header"); } if (!li_request_validate_header(con)) { /* set status 400 if not already set */ if (vr->response.http_status == 0) vr->response.http_status = 400; con->state = LI_CON_STATE_WRITE; con->info.keep_alive = FALSE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } /* When does a client ask for 100 Continue? probably not while trying to ddos us * as post content probably goes to a dynamic backend anyway, we don't * care about the rare cases we could determine that we don't want a request at all * before sending it to a backend - so just send the stupid header */ if (con->expect_100_cont) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "send 100 Continue"); } li_chunkqueue_append_mem(con->out.out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n")); con->expect_100_cont = FALSE; li_stream_notify(&con->out); } con->state = LI_CON_STATE_HANDLE_MAINVR; li_connection_update_io_wait(con); li_action_enter(vr, con->srv->mainaction); li_vrequest_handle_request_headers(vr); } if (con->state != LI_CON_STATE_READ_REQUEST_HEADER && !in->is_closed) { goffset newbytes = 0; if (-1 == vr->request.content_length) { if (!in->is_closed) { if (!li_filter_chunked_decode(vr, in, raw_in, &con->in_chunked_decode_state)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "failed decoding chunked request body"); } li_connection_error(con); return; } if (in->is_closed) vr->request.content_length = in->bytes_in; newbytes = 1; /* always notify */ } } else { if (in->bytes_in < vr->request.content_length) { newbytes = li_chunkqueue_steal_len(in, raw_in, vr->request.content_length - in->bytes_in); } if (in->bytes_in == vr->request.content_length) { in->is_closed = TRUE; } } if (newbytes > 0 || in->is_closed) { li_stream_notify(&con->in); } } }
static liHandlerResult memcache_store_filter(liVRequest *vr, liFilter *f) { memcache_filter *mf = (memcache_filter*) f->param; if (NULL == f->in) { memcache_store_filter_free(vr, f); /* didn't handle f->in->is_closed? abort forwarding */ if (!f->out->is_closed) li_stream_reset(&f->stream); return LI_HANDLER_GO_ON; } if (NULL == mf) goto forward; if (f->in->is_closed && 0 == f->in->length && f->out->is_closed) { /* nothing to do anymore */ return LI_HANDLER_GO_ON; } /* check if size still fits into buffer */ if ((gssize) (f->in->length + mf->buf->used) > (gssize) mf->ctx->maxsize) { /* response too big, switch to "forward" mode */ memcache_store_filter_free(vr, f); goto forward; } while (0 < f->in->length) { char *data; off_t len; liChunkIter ci; liHandlerResult res; GError *err = NULL; ci = li_chunkqueue_iter(f->in); if (LI_HANDLER_GO_ON != (res = li_chunkiter_read(ci, 0, 16*1024, &data, &len, &err))) { if (NULL != err) { VR_ERROR(vr, "Couldn't read data from chunkqueue: %s", err->message); g_error_free(err); } return res; } if ((gssize) (len + mf->buf->used) > (gssize) mf->ctx->maxsize) { /* response too big, switch to "forward" mode */ memcache_store_filter_free(vr, f); goto forward; } memcpy(mf->buf->addr + mf->buf->used, data, len); mf->buf->used += len; if (!f->out->is_closed) { li_chunkqueue_steal_len(f->out, f->in, len); } else { li_chunkqueue_skip(f->in, len); } } if (f->in->is_closed) { /* finally: store response in memcached */ liMemcachedCon *con; GError *err = NULL; liMemcachedRequest *req; memcached_ctx *ctx = mf->ctx; assert(0 == f->in->length); f->out->is_closed = TRUE; con = mc_ctx_prepare(ctx, vr->wrk); mc_ctx_build_key(vr->wrk->tmp_str, ctx, vr); if (NULL != vr && CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "memcached.store: storing response for key '%s'", vr->wrk->tmp_str->str); } req = li_memcached_set(con, vr->wrk->tmp_str, ctx->flags, ctx->ttl, mf->buf, NULL, NULL, &err); memcache_store_filter_free(vr, f); if (NULL == req) { if (NULL != err) { if (NULL != vr && LI_MEMCACHED_DISABLED != err->code) { VR_ERROR(vr, "memcached.store: set failed: %s", err->message); } g_clear_error(&err); } else if (NULL != vr) { VR_ERROR(vr, "memcached.store: set failed: %s", "Unkown error"); } } } return LI_HANDLER_GO_ON; forward: if (f->out->is_closed) { li_chunkqueue_skip_all(f->in); li_stream_disconnect(&f->stream); } else { li_chunkqueue_steal_all(f->out, f->in); if (f->in->is_closed) f->out->is_closed = f->in->is_closed; } return LI_HANDLER_GO_ON; }
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; li_chunk_parser_prepare(&ctx); 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); li_chunk_parser_reset(&ctx); p = NULL; if (state->cur_chunklen > 0) { state->parse_state = 3; } else { 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_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; 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; }
static liHandlerResult cache_etag_filter_miss(liVRequest *vr, liFilter *f) { cache_etag_file *cfile = (cache_etag_file*) f->param; ssize_t res; gchar *buf; off_t buflen; liChunkIter citer; GError *err = NULL; if (NULL == f->in) { cache_etag_filter_free(vr, f); /* didn't handle f->in->is_closed? abort forwarding */ if (!f->out->is_closed) li_stream_reset(&f->stream); return LI_HANDLER_GO_ON; } if (NULL == cfile) goto forward; if (f->in->length > 0) { citer = li_chunkqueue_iter(f->in); if (LI_HANDLER_GO_ON != li_chunkiter_read(citer, 0, 64*1024, &buf, &buflen, &err)) { if (NULL != err) { if (NULL != vr) VR_ERROR(vr, "Couldn't read data from chunkqueue: %s", err->message); g_error_free(err); } else { if (NULL != vr) VR_ERROR(vr, "%s", "Couldn't read data from chunkqueue"); } cache_etag_filter_free(vr, f); goto forward; } res = write(cfile->fd, buf, buflen); if (res < 0) { switch (errno) { case EINTR: case EAGAIN: return LI_HANDLER_COMEBACK; default: if (NULL != vr) VR_ERROR(vr, "Couldn't write to temporary cache file '%s': %s", cfile->tmpfilename->str, g_strerror(errno)); cache_etag_filter_free(vr, f); goto forward; } } else { if (!f->out->is_closed) { li_chunkqueue_steal_len(f->out, f->in, res); } else { li_chunkqueue_skip(f->in, res); } } } if (0 == f->in->length && f->in->is_closed) { f->out->is_closed = TRUE; f->param = NULL; cache_etag_file_finish(vr, cfile); return LI_HANDLER_GO_ON; } return LI_HANDLER_GO_ON; forward: if (f->out->is_closed) { li_chunkqueue_skip_all(f->in); li_stream_disconnect(&f->stream); } else { li_chunkqueue_steal_all(f->out, f->in); if (f->in->is_closed) f->out->is_closed = f->in->is_closed; } return LI_HANDLER_GO_ON; }