static void test_partition(abts_case *tc, void *data) { apr_bucket_alloc_t *ba = apr_bucket_alloc_create(p); apr_bucket_brigade *bb = apr_brigade_create(p, ba); apr_bucket *e; e = apr_bucket_immortal_create(hello, strlen(hello), ba); APR_BRIGADE_INSERT_HEAD(bb, e); apr_assert_success(tc, "partition brigade", apr_brigade_partition(bb, 5, &e)); test_bucket_content(tc, APR_BRIGADE_FIRST(bb), "hello", 5); test_bucket_content(tc, APR_BRIGADE_LAST(bb), ", world", 7); ABTS_ASSERT(tc, "partition returns APR_INCOMPLETE", apr_brigade_partition(bb, 8192, &e)); ABTS_ASSERT(tc, "APR_INCOMPLETE partition returned sentinel", e == APR_BRIGADE_SENTINEL(bb)); apr_brigade_destroy(bb); apr_bucket_alloc_destroy(ba); }
static int php_apache_sapi_read_post(char *buf, uint count_bytes) { apr_size_t len; php_struct *ctx = SG(server_context); apr_bucket_brigade *brigade; apr_bucket *partition; brigade = ctx->post_data; len = count_bytes; switch (apr_brigade_partition(ctx->post_data, count_bytes, &partition)) { case APR_SUCCESS: apr_brigade_flatten(ctx->post_data, buf, &len); brigade = apr_brigade_split(ctx->post_data, partition); apr_brigade_destroy(ctx->post_data); ctx->post_data = brigade; break; case APR_INCOMPLETE: apr_brigade_flatten(ctx->post_data, buf, &len); apr_brigade_cleanup(ctx->post_data); break; } return len; }
static int brigade_partition(lua_State*L) { apr_bucket_brigade *bb = (apr_bucket_brigade *)CHECK_BUCKETBRIGADE_OBJECT(1); apr_off_t point = luaL_checkint(L,2); apr_bucket * b = NULL; apr_status_t rc = apr_brigade_partition(bb,point, &b); if(rc==APR_SUCCESS) { PUSH_BUCKET_OBJECT(b); }else { lua_pushnil(L); } lua_pushinteger(L, rc); return 2; }
static apr_status_t rate_limit_filter(ap_filter_t *f, apr_bucket_brigade *input_bb) { apr_status_t rv = APR_SUCCESS; rl_ctx_t *ctx = f->ctx; apr_bucket *fb; int do_sleep = 0; apr_bucket_alloc_t *ba = f->r->connection->bucket_alloc; apr_bucket_brigade *bb = input_bb; if (f->c->aborted) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01454) "rl: conn aborted"); apr_brigade_cleanup(bb); return APR_ECONNABORTED; } if (ctx == NULL) { const char *rl = NULL; int ratelimit; /* no subrequests. */ if (f->r->main != NULL) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } rl = apr_table_get(f->r->subprocess_env, "rate-limit"); if (rl == NULL) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* rl is in kilo bytes / second */ ratelimit = atoi(rl) * 1024; if (ratelimit <= 0) { /* remove ourselves */ ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* first run, init stuff */ ctx = apr_palloc(f->r->pool, sizeof(rl_ctx_t)); f->ctx = ctx; ctx->state = RATE_LIMIT; ctx->speed = ratelimit; /* calculate how many bytes / interval we want to send */ /* speed is bytes / second, so, how many (speed / 1000 % interval) */ ctx->chunk_size = (ctx->speed / (1000 / RATE_INTERVAL_MS)); ctx->tmpbb = apr_brigade_create(f->r->pool, ba); ctx->holdingbb = apr_brigade_create(f->r->pool, ba); } while (ctx->state != RATE_ERROR && (!APR_BRIGADE_EMPTY(bb) || !APR_BRIGADE_EMPTY(ctx->holdingbb))) { apr_bucket *e; if (!APR_BRIGADE_EMPTY(ctx->holdingbb)) { APR_BRIGADE_CONCAT(bb, ctx->holdingbb); } while (ctx->state == RATE_FULLSPEED && !APR_BRIGADE_EMPTY(bb)) { /* Find where we 'stop' going full speed. */ for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (AP_RL_BUCKET_IS_END(e)) { apr_bucket *f; f = APR_RING_LAST(&bb->list); APR_RING_UNSPLICE(e, f, link); APR_RING_SPLICE_TAIL(&ctx->holdingbb->list, e, f, apr_bucket, link); ctx->state = RATE_LIMIT; break; } } if (f->c->aborted) { apr_brigade_cleanup(bb); ctx->state = RATE_ERROR; break; } fb = apr_bucket_flush_create(ba); APR_BRIGADE_INSERT_TAIL(bb, fb); rv = ap_pass_brigade(f->next, bb); if (rv != APR_SUCCESS) { ctx->state = RATE_ERROR; ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01455) "rl: full speed brigade pass failed."); } } while (ctx->state == RATE_LIMIT && !APR_BRIGADE_EMPTY(bb)) { for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (AP_RL_BUCKET_IS_START(e)) { apr_bucket *f; f = APR_RING_LAST(&bb->list); APR_RING_UNSPLICE(e, f, link); APR_RING_SPLICE_TAIL(&ctx->holdingbb->list, e, f, apr_bucket, link); ctx->state = RATE_FULLSPEED; break; } } while (!APR_BRIGADE_EMPTY(bb)) { apr_bucket *stop_point; apr_off_t len = 0; if (f->c->aborted) { apr_brigade_cleanup(bb); ctx->state = RATE_ERROR; break; } if (do_sleep) { apr_sleep(RATE_INTERVAL_MS * 1000); } else { do_sleep = 1; } apr_brigade_length(bb, 1, &len); rv = apr_brigade_partition(bb, ctx->chunk_size, &stop_point); if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { ctx->state = RATE_ERROR; ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01456) "rl: partition failed."); break; } if (stop_point != APR_BRIGADE_SENTINEL(bb)) { apr_bucket *f; apr_bucket *e = APR_BUCKET_PREV(stop_point); f = APR_RING_FIRST(&bb->list); APR_RING_UNSPLICE(f, e, link); APR_RING_SPLICE_HEAD(&ctx->tmpbb->list, f, e, apr_bucket, link); } else { APR_BRIGADE_CONCAT(ctx->tmpbb, bb); } fb = apr_bucket_flush_create(ba); APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, fb); #if 0 brigade_dump(f->r, ctx->tmpbb); brigade_dump(f->r, bb); #endif rv = ap_pass_brigade(f->next, ctx->tmpbb); apr_brigade_cleanup(ctx->tmpbb); if (rv != APR_SUCCESS) { ctx->state = RATE_ERROR; ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01457) "rl: brigade pass failed."); break; } } } } return rv; }
static apr_status_t mod_dechunk_replay_kept_body( ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { apr_bucket *ec, *e2; replay_ctx_t *ctx = f->ctx; apr_status_t status; /* just get out of the way of things we don't want. */ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { return ap_get_brigade(f->next, b, mode, block, readbytes); } /* mod_dechunk is finished, send next filter */ if (ctx->remaining <= 0) { return ap_get_brigade(f->next, b, mode, block, readbytes); } if (readbytes > ctx->remaining) { readbytes = ctx->remaining; } status = apr_brigade_partition(ctx->kept_body, ctx->offset, &ec); if (status != APR_SUCCESS) { ap_log_rerror( APLOG_MARK, APLOG_ERR, status, f->r, "apr_brigade_partition() failed at offset %" APR_OFF_T_FMT, ctx->offset); return status; } status = apr_brigade_partition(ctx->kept_body, ctx->offset + readbytes, &e2); if (status != APR_SUCCESS) { ap_log_rerror( APLOG_MARK, APLOG_ERR, status, f->r, "apr_brigade_partition() failed at offset + readbytes %" APR_OFF_T_FMT, ctx->offset + readbytes); return status; } do { apr_bucket *tmp; apr_bucket_copy(ec, &tmp); APR_BRIGADE_INSERT_TAIL(b, tmp); ec = APR_BUCKET_NEXT(ec); } while (ec != e2); ctx->remaining -= readbytes; ctx->offset += readbytes; return APR_SUCCESS; }
static apr_status_t rate_limit_filter(ap_filter_t *f, apr_bucket_brigade *bb) { apr_status_t rv = APR_SUCCESS; rl_ctx_t *ctx = f->ctx; apr_bucket_alloc_t *ba = f->r->connection->bucket_alloc; /* Set up our rl_ctx_t on first use */ if (ctx == NULL) { const char *rl = NULL; int ratelimit; int burst = 0; /* no subrequests. */ if (f->r->main != NULL) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* Configuration: rate limit */ rl = apr_table_get(f->r->subprocess_env, "rate-limit"); if (rl == NULL) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* rl is in kilo bytes / second */ ratelimit = atoi(rl) * 1024; if (ratelimit <= 0) { /* remove ourselves */ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(03488) "rl: disabling: rate-limit = %s (too high?)", rl); ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* Configuration: optional initial burst */ rl = apr_table_get(f->r->subprocess_env, "rate-initial-burst"); if (rl != NULL) { burst = atoi(rl) * 1024; if (burst <= 0) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(03489) "rl: disabling burst: rate-initial-burst = %s (too high?)", rl); burst = 0; } } /* Set up our context */ ctx = apr_palloc(f->r->pool, sizeof(rl_ctx_t)); f->ctx = ctx; ctx->state = RATE_LIMIT; ctx->speed = ratelimit; ctx->burst = burst; ctx->do_sleep = 0; /* calculate how many bytes / interval we want to send */ /* speed is bytes / second, so, how many (speed / 1000 % interval) */ ctx->chunk_size = (ctx->speed / (1000 / RATE_INTERVAL_MS)); ctx->tmpbb = apr_brigade_create(f->r->pool, ba); ctx->holdingbb = apr_brigade_create(f->r->pool, ba); } else { APR_BRIGADE_PREPEND(bb, ctx->holdingbb); } while (!APR_BRIGADE_EMPTY(bb)) { apr_bucket *e; if (ctx->state == RATE_FULLSPEED) { /* Find where we 'stop' going full speed. */ for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (AP_RL_BUCKET_IS_END(e)) { apr_brigade_split_ex(bb, e, ctx->holdingbb); ctx->state = RATE_LIMIT; break; } } e = apr_bucket_flush_create(ba); APR_BRIGADE_INSERT_TAIL(bb, e); rv = ap_pass_brigade(f->next, bb); apr_brigade_cleanup(bb); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01455) "rl: full speed brigade pass failed."); return rv; } } else { for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (AP_RL_BUCKET_IS_START(e)) { apr_brigade_split_ex(bb, e, ctx->holdingbb); ctx->state = RATE_FULLSPEED; break; } } while (!APR_BRIGADE_EMPTY(bb)) { apr_off_t len = ctx->chunk_size + ctx->burst; APR_BRIGADE_CONCAT(ctx->tmpbb, bb); /* * Pull next chunk of data; the initial amount is our * burst allotment (if any) plus a chunk. All subsequent * iterations are just chunks with whatever remaining * burst amounts we have left (in case not done in the * first bucket). */ rv = apr_brigade_partition(ctx->tmpbb, len, &e); if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01456) "rl: partition failed."); return rv; } /* Send next metadata now if any */ while (e != APR_BRIGADE_SENTINEL(ctx->tmpbb) && APR_BUCKET_IS_METADATA(e)) { e = APR_BUCKET_NEXT(e); } if (e != APR_BRIGADE_SENTINEL(ctx->tmpbb)) { apr_brigade_split_ex(ctx->tmpbb, e, bb); } else { apr_brigade_length(ctx->tmpbb, 1, &len); } /* * Adjust the burst amount depending on how much * we've done up to now. */ if (ctx->burst) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(03485) "rl: burst %d; len %"APR_OFF_T_FMT, ctx->burst, len); if (len < ctx->burst) { ctx->burst -= len; } else { ctx->burst = 0; } } e = APR_BRIGADE_LAST(ctx->tmpbb); if (APR_BUCKET_IS_EOS(e)) { ap_remove_output_filter(f); } else if (!APR_BUCKET_IS_FLUSH(e)) { if (APR_BRIGADE_EMPTY(bb)) { /* Wait for more (or next call) */ break; } e = apr_bucket_flush_create(ba); APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, e); } #if defined(RLFDEBUG) brigade_dump(f->r, ctx->tmpbb); brigade_dump(f->r, bb); #endif /* RLFDEBUG */ if (ctx->do_sleep) { apr_sleep(RATE_INTERVAL_MS * 1000); } else { ctx->do_sleep = 1; } rv = ap_pass_brigade(f->next, ctx->tmpbb); apr_brigade_cleanup(ctx->tmpbb); if (rv != APR_SUCCESS) { /* Most often, user disconnects from stream */ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01457) "rl: brigade pass failed."); return rv; } } } if (!APR_BRIGADE_EMPTY(ctx->holdingbb)) { /* Any rate-limited data in tmpbb is sent unlimited along * with the rest. */ APR_BRIGADE_CONCAT(bb, ctx->tmpbb); APR_BRIGADE_CONCAT(bb, ctx->holdingbb); } } #if defined(RLFDEBUG) brigade_dump(f->r, ctx->tmpbb); #endif /* RLFDEBUG */ /* Save remaining tmpbb with the correct lifetime for the next call */ return ap_save_brigade(f, &ctx->holdingbb, &ctx->tmpbb, f->r->pool); }
apr_status_t ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { apr_status_t rv; core_net_rec *net = f->ctx; core_ctx_t *ctx = net->in_ctx; const char *str; apr_size_t len; if (mode == AP_MODE_INIT) { /* * this mode is for filters that might need to 'initialize' * a connection before reading request data from a client. * NNTP over SSL for example needs to handshake before the * server sends the welcome message. * such filters would have changed the mode before this point * is reached. however, protocol modules such as NNTP should * not need to know anything about SSL. given the example, if * SSL is not in the filter chain, AP_MODE_INIT is a noop. */ return APR_SUCCESS; } if (!ctx) { net->in_ctx = ctx = apr_palloc(f->c->pool, sizeof(*ctx)); ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc); ctx->tmpbb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); /* seed the brigade with the client socket. */ rv = ap_run_insert_network_bucket(f->c, ctx->b, net->client_socket); if (rv != APR_SUCCESS) return rv; } else if (APR_BRIGADE_EMPTY(ctx->b)) { return APR_EOF; } /* ### This is bad. */ BRIGADE_NORMALIZE(ctx->b); /* check for empty brigade again *AFTER* BRIGADE_NORMALIZE() * If we have lost our socket bucket (see above), we are EOF. * * Ideally, this should be returning SUCCESS with EOS bucket, but * some higher-up APIs (spec. read_request_line via ap_rgetline) * want an error code. */ if (APR_BRIGADE_EMPTY(ctx->b)) { return APR_EOF; } if (mode == AP_MODE_GETLINE) { /* we are reading a single LF line, e.g. the HTTP headers */ rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN); /* We should treat EAGAIN here the same as we do for EOF (brigade is * empty). We do this by returning whatever we have read. This may * or may not be bogus, but is consistent (for now) with EOF logic. */ if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) { rv = APR_SUCCESS; } return rv; } /* ### AP_MODE_PEEK is a horrific name for this mode because we also * eat any CRLFs that we see. That's not the obvious intention of * this mode. Determine whether anyone actually uses this or not. */ if (mode == AP_MODE_EATCRLF) { apr_bucket *e; const char *c; /* The purpose of this loop is to ignore any CRLF (or LF) at the end * of a request. Many browsers send extra lines at the end of POST * requests. We use the PEEK method to determine if there is more * data on the socket, so that we know if we should delay sending the * end of one request until we have served the second request in a * pipelined situation. We don't want to actually delay sending a * response if the server finds a CRLF (or LF), becuause that doesn't * mean that there is another request, just a blank line. */ while (1) { if (APR_BRIGADE_EMPTY(ctx->b)) return APR_EOF; e = APR_BRIGADE_FIRST(ctx->b); rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ); if (rv != APR_SUCCESS) return rv; c = str; while (c < str + len) { if (*c == APR_ASCII_LF) c++; else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF) c += 2; else return APR_SUCCESS; } /* If we reach here, we were a bucket just full of CRLFs, so * just toss the bucket. */ /* FIXME: Is this the right thing to do in the core? */ apr_bucket_delete(e); } return APR_SUCCESS; } /* If mode is EXHAUSTIVE, we want to just read everything until the end * of the brigade, which in this case means the end of the socket. * To do this, we attach the brigade that has currently been setaside to * the brigade that was passed down, and send that brigade back. * * NOTE: This is VERY dangerous to use, and should only be done with * extreme caution. FWLIW, this would be needed by an MPM like Perchild; * such an MPM can easily request the socket and all data that has been * read, which means that it can pass it to the correct child process. */ if (mode == AP_MODE_EXHAUSTIVE) { apr_bucket *e; /* Tack on any buckets that were set aside. */ APR_BRIGADE_CONCAT(b, ctx->b); /* Since we've just added all potential buckets (which will most * likely simply be the socket bucket) we know this is the end, * so tack on an EOS too. */ /* We have read until the brigade was empty, so we know that we * must be EOS. */ e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); return APR_SUCCESS; } /* read up to the amount they specified. */ if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE) { apr_bucket *e; AP_DEBUG_ASSERT(readbytes > 0); e = APR_BRIGADE_FIRST(ctx->b); rv = apr_bucket_read(e, &str, &len, block); if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) { /* getting EAGAIN for a blocking read is an error; for a * non-blocking read, return an empty brigade. */ return APR_SUCCESS; } else if (rv != APR_SUCCESS) { return rv; } else if (block == APR_BLOCK_READ && len == 0) { /* We wanted to read some bytes in blocking mode. We read * 0 bytes. Hence, we now assume we are EOS. * * When we are in normal mode, return an EOS bucket to the * caller. * When we are in speculative mode, leave ctx->b empty, so * that the next call returns an EOS bucket. */ apr_bucket_delete(e); if (mode == AP_MODE_READBYTES) { e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); } return APR_SUCCESS; } /* Have we read as much data as we wanted (be greedy)? */ if (len < readbytes) { apr_size_t bucket_len; rv = APR_SUCCESS; /* We already registered the data in e in len */ e = APR_BUCKET_NEXT(e); while ((len < readbytes) && (rv == APR_SUCCESS) && (e != APR_BRIGADE_SENTINEL(ctx->b))) { /* Check for the availability of buckets with known length */ if (e->length != -1) { len += e->length; e = APR_BUCKET_NEXT(e); } else { /* * Read from bucket, but non blocking. If there isn't any * more data, well than this is fine as well, we will * not wait for more since we already got some and we are * only checking if there isn't more. */ rv = apr_bucket_read(e, &str, &bucket_len, APR_NONBLOCK_READ); if (rv == APR_SUCCESS) { len += bucket_len; e = APR_BUCKET_NEXT(e); } } } } /* We can only return at most what we read. */ if (len < readbytes) { readbytes = len; } rv = apr_brigade_partition(ctx->b, readbytes, &e); if (rv != APR_SUCCESS) { return rv; } /* Must do move before CONCAT */ ctx->tmpbb = apr_brigade_split_ex(ctx->b, e, ctx->tmpbb); if (mode == AP_MODE_READBYTES) { APR_BRIGADE_CONCAT(b, ctx->b); } else if (mode == AP_MODE_SPECULATIVE) { apr_bucket *copy_bucket; for (e = APR_BRIGADE_FIRST(ctx->b); e != APR_BRIGADE_SENTINEL(ctx->b); e = APR_BUCKET_NEXT(e)) { rv = apr_bucket_copy(e, ©_bucket); if (rv != APR_SUCCESS) { return rv; } APR_BRIGADE_INSERT_TAIL(b, copy_bucket); } } /* Take what was originally there and place it back on ctx->b */ APR_BRIGADE_CONCAT(ctx->b, ctx->tmpbb); } return APR_SUCCESS; }
apr_status_t apreq_filter(ap_filter_t *f, apr_bucket_brigade *bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { request_rec *r = f->r; struct filter_ctx *ctx; apr_status_t rv; apr_off_t len; switch (mode) { case AP_MODE_READBYTES: /* only the modes above are supported */ break; case AP_MODE_EXHAUSTIVE: /* not worth supporting at this level */ case AP_MODE_GETLINE: /* chunked trailers are b0rked in ap_http_filter */ return ap_get_brigade(f->next, bb, mode, block, readbytes); default: return APR_ENOTIMPL; } if (f->ctx == NULL) apreq_filter_make_context(f); ctx = f->ctx; if (ctx->body_status == APR_EINIT) apreq_filter_init_context(f); if (ctx->spool && !APR_BRIGADE_EMPTY(ctx->spool)) { apr_bucket *e; rv = apr_brigade_partition(ctx->spool, readbytes, &e); if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) return rv; if (APR_BUCKET_IS_EOS(e)) e = APR_BUCKET_NEXT(e); apreq_brigade_move(bb, ctx->spool, e); return APR_SUCCESS; } else if (ctx->body_status != APR_INCOMPLETE) { if (ctx->filter_error) return ctx->filter_error; rv = ap_get_brigade(f->next, bb, mode, block, readbytes); ap_remove_input_filter(f); return rv; } rv = ap_get_brigade(f->next, bb, mode, block, readbytes); if (rv != APR_SUCCESS) return rv; apreq_brigade_copy(ctx->bb, bb); apr_brigade_length(bb, 1, &len); ctx->bytes_read += len; if (ctx->bytes_read > ctx->read_limit) { ctx->body_status = APREQ_ERROR_OVERLIMIT; ap_log_rerror(APLOG_MARK, APLOG_ERR, ctx->body_status, r, APLOGNO(02054) "Bytes read (%" APR_UINT64_T_FMT ") exceeds configured max_body limit (%" APR_UINT64_T_FMT ")", ctx->bytes_read, ctx->read_limit); } else { ctx->body_status = apreq_parser_run(ctx->parser, ctx->body, ctx->bb); apr_brigade_cleanup(ctx->bb); } return APR_SUCCESS; }
/** * Filter to handle a kept body on subrequests. * * If a body has been previously kept by the request, and if a subrequest wants * to re-insert the body into the request, this input filter makes it happen. */ static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { request_rec *r = f->r; apr_bucket_brigade *kept_body = r->kept_body; kept_body_ctx_t *ctx = f->ctx; apr_bucket *ec, *e2; apr_status_t rv; /* just get out of the way of things we don't want. */ if (!kept_body || (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)) { return ap_get_brigade(f->next, b, mode, block, readbytes); } /* set up the context if it does not already exist */ if (!ctx) { f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx)); ctx->offset = 0; apr_brigade_length(kept_body, 1, &ctx->remaining); } /* kept_body is finished, send next filter */ if (ctx->remaining <= 0) { return ap_get_brigade(f->next, b, mode, block, readbytes); } /* send all of the kept_body, but no more */ if (readbytes > ctx->remaining) { readbytes = ctx->remaining; } /* send part of the kept_body */ if ((rv = apr_brigade_partition(kept_body, ctx->offset, &ec)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01414) "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset); return rv; } if ((rv = apr_brigade_partition(kept_body, ctx->offset + readbytes, &e2)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01415) "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset + readbytes); return rv; } do { apr_bucket *foo; const char *str; apr_size_t len; if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) { /* As above; this should not fail since the bucket has * a known length, but just to be sure, this takes * care of uncopyable buckets that do somehow manage * to slip through. */ /* XXX: check for failure? */ apr_bucket_read(ec, &str, &len, APR_BLOCK_READ); apr_bucket_copy(ec, &foo); } APR_BRIGADE_INSERT_TAIL(b, foo); ec = APR_BUCKET_NEXT(ec); } while (ec != e2); ctx->remaining -= readbytes; ctx->offset += readbytes; return APR_SUCCESS; }
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, apr_bucket_brigade *bb) { #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1) request_rec *r = f->r; conn_rec *c = r->connection; byterange_ctx *ctx; apr_bucket *e; apr_bucket_brigade *bsend; apr_off_t range_start; apr_off_t range_end; char *current; apr_off_t clength = 0; apr_status_t rv; int found = 0; int num_ranges; /* Iterate through the brigade until reaching EOS or a bucket with * unknown length. */ for (e = APR_BRIGADE_FIRST(bb); (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e) && e->length != (apr_size_t)-1); e = APR_BUCKET_NEXT(e)) { clength += e->length; } /* Don't attempt to do byte range work if this brigade doesn't * contain an EOS, or if any of the buckets has an unknown length; * this avoids the cases where it is expensive to perform * byteranging (i.e. may require arbitrary amounts of memory). */ if (!APR_BUCKET_IS_EOS(e) || clength <= 0) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } num_ranges = ap_set_byterange(r); /* We have nothing to do, get out of the way. */ if (num_ranges == 0) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } ctx = apr_pcalloc(r->pool, sizeof(*ctx)); ctx->num_ranges = num_ranges; /* create a brigade in case we never call ap_save_brigade() */ ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); if (ctx->num_ranges > 1) { /* Is ap_make_content_type required here? */ const char *orig_ct = ap_make_content_type(r, r->content_type); ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx", (apr_uint64_t)r->request_time, (long) getpid()); ap_set_content_type(r, apr_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/", "byteranges; boundary=", ctx->boundary, NULL)); if (strcasecmp(orig_ct, NO_CONTENT_TYPE)) { ctx->bound_head = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, CRLF "Content-type: ", orig_ct, CRLF "Content-range: bytes ", NULL); } else { /* if we have no type for the content, do our best */ ctx->bound_head = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, CRLF "Content-range: bytes ", NULL); } ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head)); } /* this brigade holds what we will be sending */ bsend = apr_brigade_create(r->pool, c->bucket_alloc); while ((current = ap_getword(r->pool, &r->range, ',')) && (rv = parse_byterange(current, clength, &range_start, &range_end))) { apr_bucket *e2; apr_bucket *ec; if (rv == -1) { continue; } /* These calls to apr_brigage_partition should only fail in * pathological cases, e.g. a file being truncated whilst * being served. */ if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, PARTITION_ERR_FMT, range_start, clength); continue; } if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, PARTITION_ERR_FMT, range_end+1, clength); continue; } found = 1; /* For single range requests, we must produce Content-Range header. * Otherwise, we need to produce the multipart boundaries. */ if (ctx->num_ranges == 1) { apr_table_setn(r->headers_out, "Content-Range", apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, range_start, range_end, clength)); } else { char *ts; e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF, range_start, range_end, clength); ap_xlate_proto_to_ascii(ts, strlen(ts)); e = apr_bucket_pool_create(ts, strlen(ts), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); } do { apr_bucket *foo; const char *str; apr_size_t len; if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) { /* As above; this should not fail since the bucket has * a known length, but just to be sure, this takes * care of uncopyable buckets that do somehow manage * to slip through. */ /* XXX: check for failure? */ apr_bucket_read(ec, &str, &len, APR_BLOCK_READ); apr_bucket_copy(ec, &foo); } APR_BRIGADE_INSERT_TAIL(bsend, foo); ec = APR_BUCKET_NEXT(ec); } while (ec != e2); } if (found == 0) { ap_remove_output_filter(f); r->status = HTTP_OK; /* bsend is assumed to be empty if we get here. */ e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL, r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); return ap_pass_brigade(f->next, bsend); } if (ctx->num_ranges > 1) { char *end; /* add the final boundary */ end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL); ap_xlate_proto_to_ascii(end, strlen(end)); e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); } e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); /* we're done with the original content - all of our data is in bsend. */ apr_brigade_cleanup(bb); /* send our multipart output */ return ap_pass_brigade(f->next, bsend); }