liHandlerResult li_chunk_parser_next(liChunkParserCtx *ctx, char **p, char **pe, GError **err) { off_t l; liHandlerResult res; g_return_val_if_fail (err == NULL || *err == NULL, LI_HANDLER_ERROR); if (NULL == ctx->curi.element) return LI_HANDLER_WAIT_FOR_EVENT; while (ctx->start >= (l = li_chunkiter_length(ctx->curi))) { liChunkIter i = ctx->curi; /* Wait at the end of the last chunk if it gets extended */ if (!li_chunkiter_next(&i)) return LI_HANDLER_WAIT_FOR_EVENT; ctx->curi = i; ctx->start -= l; } if (NULL == ctx->curi.element) return LI_HANDLER_WAIT_FOR_EVENT; if (LI_HANDLER_GO_ON != (res = li_chunkiter_read(ctx->curi, ctx->start, l - ctx->start, &ctx->buf, &ctx->length, err))) { return res; } *p = ctx->buf; *pe = ctx->buf + ctx->length; return LI_HANDLER_GO_ON; }
gboolean li_chunkqueue_extract_to_bytearr(liVRequest *vr, liChunkQueue *cq, goffset len, GByteArray *dest) { liChunkIter ci; goffset coff, clen; g_byte_array_set_size(dest, 0); if (len > cq->length) return FALSE; g_byte_array_set_size(dest, len); g_byte_array_set_size(dest, 0); ci = li_chunkqueue_iter(cq); while (len > 0) { coff = 0; clen = li_chunkiter_length(ci); while (coff < clen) { gchar *buf; off_t we_have; if (LI_HANDLER_GO_ON != li_chunkiter_read(vr, ci, coff, len, &buf, &we_have)) goto error; g_byte_array_append(dest, (guint8*) buf, we_have); coff += we_have; len -= we_have; if (len <= 0) return TRUE; } li_chunkiter_next(&ci); } return TRUE; error: g_byte_array_set_size(dest, 0); return FALSE; }
gboolean li_chunk_extract_to(liChunkParserMark from, liChunkParserMark to, GString *dest, GError **err) { liChunkParserMark i; g_return_val_if_fail (err == NULL || *err == NULL, FALSE); g_string_set_size(dest, to.abs_pos - from.abs_pos); li_g_string_clear(dest); for ( i = from; i.ci.element != to.ci.element; li_chunkiter_next(&i.ci) ) { goffset len = li_chunkiter_length(i.ci); while (i.pos < len) { char *buf; off_t we_have; if (LI_HANDLER_GO_ON != li_chunkiter_read(i.ci, i.pos, len - i.pos, &buf, &we_have, err)) goto error; if (dest->len + we_have < dest->allocated_len) { /* "fast" append */ memcpy(dest->str + dest->len, buf, we_have); dest->len += we_have; dest->str[dest->len] = '\0'; } else { g_string_append_len(dest, buf, we_have); } i.pos += we_have; } i.pos = 0; } while (i.pos < to.pos) { char *buf; off_t we_have; if (LI_HANDLER_GO_ON != li_chunkiter_read(i.ci, i.pos, to.pos - i.pos, &buf, &we_have, err)) goto error; if (dest->len + we_have < dest->allocated_len) { /* "fast" append */ memcpy(dest->str + dest->len, buf, we_have); dest->len += we_have; dest->str[dest->len] = '\0'; } else { g_string_append_len(dest, buf, we_have); } i.pos += we_have; } return TRUE; error: li_g_string_clear(dest); 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 = 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; }
liNetworkStatus li_network_backend_write(liVRequest *vr, int fd, liChunkQueue *cq, goffset *write_max) { const ssize_t blocksize = 16*1024; /* 16k */ char *block_data; off_t block_len; ssize_t r; gboolean did_write_something = FALSE; liChunkIter ci; do { if (0 == cq->length) return did_write_something ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_FATAL_ERROR; ci = li_chunkqueue_iter(cq); switch (li_chunkiter_read(vr, ci, 0, blocksize, &block_data, &block_len)) { case LI_HANDLER_GO_ON: break; case LI_HANDLER_ERROR: default: return LI_NETWORK_STATUS_FATAL_ERROR; } if (-1 == (r = li_net_write(fd, block_data, block_len))) { switch (errno) { case EAGAIN: #if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif return did_write_something ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT; case ECONNRESET: case EPIPE: case ETIMEDOUT: return LI_NETWORK_STATUS_CONNECTION_CLOSE; default: VR_ERROR(vr, "oops, write to fd=%d failed: %s", fd, g_strerror(errno)); return LI_NETWORK_STATUS_FATAL_ERROR; } } else if (0 == r) { return did_write_something ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT; } li_chunkqueue_skip(cq, r); did_write_something = TRUE; *write_max -= r; } while (r == block_len && *write_max > 0); return LI_NETWORK_STATUS_SUCCESS; }
liHandlerResult li_chunk_parser_next(liVRequest *vr, liChunkParserCtx *ctx, char **p, char **pe) { off_t l; liHandlerResult res; if (NULL == ctx->curi.element) return LI_HANDLER_WAIT_FOR_EVENT; while (ctx->start >= (l = li_chunkiter_length(ctx->curi))) { liChunkIter i = ctx->curi; /* Wait at the end of the last chunk if it gets extended */ if (!li_chunkiter_next(&i)) return LI_HANDLER_WAIT_FOR_EVENT; ctx->curi = i; ctx->start -= l; } if (NULL == ctx->curi.element) return LI_HANDLER_WAIT_FOR_EVENT; if (LI_HANDLER_GO_ON != (res = li_chunkiter_read(vr, ctx->curi, ctx->start, l - ctx->start, &ctx->buf, &ctx->length))) { return res; } *p = ctx->buf; *pe = ctx->buf + ctx->length; return LI_HANDLER_GO_ON; }
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; }
static void do_gnutls_write(liGnuTLSFilter *f) { const ssize_t blocksize = 16*1024; /* 16k */ char *block_data; off_t block_len; ssize_t r; off_t write_max; #ifdef USE_CORK gboolean corked = FALSE; #endif liChunkQueue *cq = f->plain_drain.out; f_acquire(f); f->write_wants_read = FALSE; /* use space in (encrypted) outgoing buffer as amounts of bytes we try to write from (plain) output * don't care if we write a little bit more than the limit allowed */ write_max = li_chunkqueue_limit_available(f->crypt_source.out); LI_FORCE_ASSERT(write_max >= 0); /* we set a limit! */ if (0 == write_max) goto out; /* if we start writing, try to write at least blocksize bytes */ if (write_max < blocksize) write_max = blocksize; if (NULL != f->session && !f->initial_handshaked_finished && !do_gnutls_handshake(f, TRUE)) goto out; if (NULL == f->session) { f_abort_gnutls(f); goto out; } #ifdef USE_CORK if (0 != cq->length && cq->queue.length > 1) { corked = TRUE; gnutls_record_cork(f->session); } #endif do { GError *err = NULL; liChunkIter ci; if (0 == cq->length) break; ci = li_chunkqueue_iter(cq); switch (li_chunkiter_read(ci, 0, blocksize, &block_data, &block_len, &err)) { case LI_HANDLER_GO_ON: break; case LI_HANDLER_ERROR: if (NULL != err) { _ERROR(f->srv, f->wrk, f->log_context, "Couldn't read data from chunkqueue: %s", err->message); g_error_free(err); } /* fall through */ default: f_abort_gnutls(f); goto out; } r = gnutls_record_send(f->session, block_data, block_len); if (r <= 0) { do_handle_error(f, "gnutls_record_send", r, TRUE); goto out; } li_chunkqueue_skip(cq, r); write_max -= r; } while (r == block_len && write_max > 0); if (cq->is_closed && 0 == cq->length) { r = gnutls_bye(f->session, GNUTLS_SHUT_RDWR); switch (r) { case GNUTLS_E_SUCCESS: case GNUTLS_E_AGAIN: case GNUTLS_E_INTERRUPTED: f->plain_source.out->is_closed = TRUE; f->crypt_source.out->is_closed = TRUE; f->crypt_drain.out->is_closed = TRUE; li_stream_disconnect(&f->crypt_source); /* plain in -> crypt out */ f_close_gnutls(f); break; default: do_handle_error(f, "gnutls_bye", r, TRUE); f_abort_gnutls(f); break; } } else if (0 < cq->length && 0 != li_chunkqueue_limit_available(f->crypt_source.out)) { li_stream_again_later(&f->plain_drain); } out: #ifdef USE_CORK if (NULL != f->session && corked) { corked = TRUE; gnutls_record_uncork(f->session, 0); } #endif f_release(f); }
static void bod_handle_data(bod_state *state) { liChunkQueue *out = state->stream.out; liChunkQueue *in; if (out->is_closed) { li_stream_disconnect(&state->stream); bod_close(state); return; } in = (state->stream.source != NULL) ? state->stream.source->out : NULL; if (NULL == in) goto out; if (NULL == state->vr) { li_chunkqueue_steal_all(out, in); goto out; } while (in->length > 0) { liChunk *c = li_chunkqueue_first_chunk(in); liChunkIter ci; off_t length, data_len; char *data = NULL; GError *err; assert(UNUSED_CHUNK != c->type); switch (c->type) { case UNUSED_CHUNK: /* shouldn't happen anyway, but stealing it is ok here too */ case FILE_CHUNK: if (state->split_on_file_chunks) { bod_close(state); } else { bod_flush(state); } li_chunkqueue_steal_chunk(out, in); break; case STRING_CHUNK: case MEM_CHUNK: case BUFFER_CHUNK: if (!bod_open(state)) return; length = li_chunk_length(c); ci = li_chunkqueue_iter(in); err = NULL; if (LI_HANDLER_GO_ON != li_chunkiter_read(ci, 0, length, &data, &data_len, &err)) { if (NULL != err) { VR_ERROR(state->vr, "%s", err->message); g_error_free(err); } bod_error(state); return; } while ( data_len > 0 ) { ssize_t r; r = pwrite(state->tempfile->fd, data, data_len, state->write_pos); if (r < 0) { switch (errno) { case EINTR: continue; default: break; } VR_ERROR(state->vr, "pwrite failed: %s", g_strerror(errno)); bod_stop(state); /* write failures are not critical */ return; } data += r; data_len -= r; state->write_pos += r; } li_chunkqueue_skip(in, length); break; } } bod_autoflush(state); out: if (NULL == in || in->is_closed) { out->is_closed = TRUE; bod_close(state); /* close/flush ignores out->is_closed */ li_stream_notify(&state->stream); /* if no flush happened we still notify */ } }
liHandlerResult li_filter_buffer_on_disk(liVRequest *vr, liChunkQueue *out, liChunkQueue *in, bod_state *state) { UNUSED(vr); if (out->is_closed) { in->is_closed = TRUE; li_chunkqueue_skip_all(in); bod_close(state); return LI_HANDLER_GO_ON; } while (in->length > 0) { liChunk *c = li_chunkqueue_first_chunk(in); liChunkIter ci; off_t length, data_len; char *data = NULL; GError *err; switch (c->type) { case UNUSED_CHUNK: return LI_HANDLER_ERROR; case FILE_CHUNK: bod_flush(out, state); if (state->split_on_file_chunks) { bod_close(state); } li_chunkqueue_steal_chunk(out, in); break; case STRING_CHUNK: case MEM_CHUNK: case BUFFER_CHUNK: if (!bod_open(vr, state)) return LI_HANDLER_ERROR; length = li_chunk_length(c); ci = li_chunkqueue_iter(in); err = NULL; if (LI_HANDLER_GO_ON != li_chunkiter_read(ci, 0, length, &data, &data_len, &err)) { if (NULL != err) { VR_ERROR(vr, "%s", err->message); g_error_free(err); } return LI_HANDLER_ERROR; } while ( data_len > 0 ) { ssize_t r; r = pwrite(state->tempfile->fd, data, data_len, state->write_pos); if (r < 0) { switch (errno) { case EINTR: continue; default: break; } VR_ERROR(vr, "pwrite failed: %s", g_strerror(errno)); return LI_HANDLER_ERROR; } data += r; data_len -= r; state->write_pos += r; } li_chunkqueue_skip(in, length); break; } } bod_autoflush(out, state); if (in->is_closed) { bod_flush(out, state); out->is_closed = TRUE; bod_close(state); return LI_HANDLER_GO_ON; } return LI_HANDLER_GO_ON; }
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; }