| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <apr_pools.h> |
| #include <apr_strings.h> |
| #include <apr_date.h> |
| |
| #include "serf.h" |
| #include "serf_bucket_util.h" |
| |
| #include "serf_private.h" |
| |
| typedef struct request_context_t { |
| const char *method; |
| const char *uri; |
| serf_bucket_t *headers; |
| serf_bucket_t *body; |
| apr_int64_t len; |
| serf_config_t *config; |
| } request_context_t; |
| |
| #define LENGTH_UNKNOWN ((apr_int64_t)-1) |
| |
| |
| serf_bucket_t *serf_bucket_request_create( |
| const char *method, |
| const char *URI, |
| serf_bucket_t *body, |
| serf_bucket_alloc_t *allocator) |
| { |
| request_context_t *ctx; |
| |
| ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); |
| ctx->method = method; |
| ctx->uri = URI; |
| ctx->headers = serf_bucket_headers_create(allocator); |
| ctx->body = body; |
| ctx->len = LENGTH_UNKNOWN; |
| ctx->config = NULL; |
| |
| return serf_bucket_create(&serf_bucket_type_request, allocator, ctx); |
| } |
| |
| void serf_bucket_request_set_CL( |
| serf_bucket_t *bucket, |
| apr_int64_t len) |
| { |
| request_context_t *ctx = (request_context_t *)bucket->data; |
| |
| ctx->len = len; |
| } |
| |
| serf_bucket_t *serf_bucket_request_get_headers( |
| serf_bucket_t *bucket) |
| { |
| return ((request_context_t *)bucket->data)->headers; |
| } |
| |
| void serf__bucket_request_read(serf_bucket_t *request_bucket, |
| serf_bucket_t **body_bkt, |
| const char **uri, |
| const char **method) |
| { |
| request_context_t *ctx = request_bucket->data; |
| |
| if (body_bkt) |
| *body_bkt = ctx->body; |
| if (uri) |
| *uri = ctx->uri; |
| if (method) |
| *method = ctx->method; |
| } |
| |
| |
| void serf_bucket_request_set_root( |
| serf_bucket_t *bucket, |
| const char *root_url) |
| { |
| request_context_t *ctx = (request_context_t *)bucket->data; |
| |
| /* If uri is already absolute, don't change it. */ |
| if (ctx->uri[0] != '/') |
| return; |
| |
| /* If uri is '/' replace it with root_url. */ |
| if (ctx->uri[1] == '\0') |
| ctx->uri = root_url; |
| else |
| ctx->uri = |
| apr_pstrcat(serf_bucket_allocator_get_pool(bucket->allocator), |
| root_url, |
| ctx->uri, |
| NULL); |
| } |
| |
| static void serialize_data(serf_bucket_t *bucket) |
| { |
| request_context_t *ctx = bucket->data; |
| serf_bucket_t *new_bucket; |
| struct iovec iov[4]; |
| |
| /* Create a bucket for the request-line. */ |
| iov[0].iov_base = (char*)ctx->method; |
| iov[0].iov_len = strlen(ctx->method); |
| iov[1].iov_base = " "; |
| iov[1].iov_len = sizeof(" ") - 1; |
| iov[2].iov_base = (char*)ctx->uri; |
| iov[2].iov_len = strlen(ctx->uri); |
| iov[3].iov_base = " HTTP/1.1\r\n"; |
| iov[3].iov_len = sizeof(" HTTP/1.1\r\n") - 1; |
| |
| new_bucket = serf_bucket_iovec_create(iov, 4, bucket->allocator); |
| |
| /* Build up the new bucket structure with the request-line and the headers. |
| * |
| * Note that self needs to become an aggregate bucket so that a |
| * pointer to self still represents the "right" data. |
| */ |
| serf_bucket_aggregate_become(bucket); |
| serf_bucket_set_config(bucket, ctx->config); |
| |
| /* Insert the two buckets. */ |
| serf_bucket_aggregate_append(bucket, new_bucket); |
| serf_bucket_aggregate_append(bucket, ctx->headers); |
| |
| /* If we know the length, then use C-L and the raw body. Otherwise, |
| use chunked encoding for the request. */ |
| if (ctx->len != LENGTH_UNKNOWN) { |
| char buf[30]; |
| apr_snprintf(buf, 30, "%" APR_INT64_T_FMT, ctx->len); |
| serf_bucket_headers_set(ctx->headers, "Content-Length", buf); |
| if (ctx->body != NULL) |
| serf_bucket_aggregate_append(bucket, ctx->body); |
| } |
| else if (ctx->body != NULL) { |
| /* Morph the body bucket to a chunked encoding bucket for now. */ |
| serf_bucket_headers_setn(ctx->headers, "Transfer-Encoding", "chunked"); |
| ctx->body = serf_bucket_chunk_create(ctx->body, bucket->allocator); |
| serf_bucket_aggregate_append(bucket, ctx->body); |
| } |
| |
| /* Our private context is no longer needed, and is not referred to by |
| * any existing bucket. Toss it. |
| */ |
| serf_bucket_mem_free(bucket->allocator, ctx); |
| } |
| |
| static apr_status_t serf_request_read(serf_bucket_t *bucket, |
| apr_size_t requested, |
| const char **data, apr_size_t *len) |
| { |
| /* Seralize our private data into a new aggregate bucket. */ |
| serialize_data(bucket); |
| |
| /* Delegate to the "new" aggregate bucket to do the read. */ |
| return bucket->type->read(bucket, requested, data, len); |
| } |
| |
| static apr_status_t serf_request_readline(serf_bucket_t *bucket, |
| int acceptable, int *found, |
| const char **data, apr_size_t *len) |
| { |
| /* Seralize our private data into a new aggregate bucket. */ |
| serialize_data(bucket); |
| |
| /* Delegate to the "new" aggregate bucket to do the readline. */ |
| return bucket->type->readline(bucket, acceptable, found, data, len); |
| } |
| |
| static apr_status_t serf_request_read_iovec(serf_bucket_t *bucket, |
| apr_size_t requested, |
| int vecs_size, |
| struct iovec *vecs, |
| int *vecs_used) |
| { |
| /* Seralize our private data into a new aggregate bucket. */ |
| serialize_data(bucket); |
| |
| /* Delegate to the "new" aggregate bucket to do the read. */ |
| return bucket->type->read_iovec(bucket, requested, |
| vecs_size, vecs, vecs_used); |
| } |
| |
| static serf_bucket_t * serf_request_read_bucket(serf_bucket_t *bucket, |
| const serf_bucket_type_t *type) |
| { |
| /* Luckily we don't have to be affraid for bucket_v2 tests here */ |
| serialize_data(bucket); |
| |
| return serf_bucket_read_bucket(bucket, type); |
| } |
| |
| static apr_status_t serf_request_peek(serf_bucket_t *bucket, |
| const char **data, |
| apr_size_t *len) |
| { |
| /* Seralize our private data into a new aggregate bucket. */ |
| serialize_data(bucket); |
| |
| /* Delegate to the "new" aggregate bucket to do the peek. */ |
| return serf_bucket_peek(bucket, data, len); |
| } |
| |
| /* Note that this function is only called when serialize_data() |
| hasn't been called on the bucket */ |
| static void serf_request_destroy(serf_bucket_t *bucket) |
| { |
| request_context_t *ctx = bucket->data; |
| |
| serf_bucket_destroy(ctx->headers); |
| |
| if (ctx->body) { |
| serf_bucket_destroy(ctx->body); |
| } |
| |
| serf_default_destroy_and_data(bucket); |
| } |
| |
| void serf_bucket_request_become( |
| serf_bucket_t *bucket, |
| const char *method, |
| const char *uri, |
| serf_bucket_t *body) |
| { |
| request_context_t *ctx; |
| |
| ctx = serf_bucket_mem_alloc(bucket->allocator, sizeof(*ctx)); |
| ctx->method = method; |
| ctx->uri = uri; |
| ctx->headers = serf_bucket_headers_create(bucket->allocator); |
| ctx->body = body; |
| |
| bucket->type = &serf_bucket_type_request; |
| bucket->data = ctx; |
| |
| /* The allocator remains the same. */ |
| } |
| |
| static apr_status_t serf_request_set_config(serf_bucket_t *bucket, |
| serf_config_t *config) |
| { |
| request_context_t *ctx = bucket->data; |
| |
| ctx->config = config; |
| |
| return serf_bucket_set_config(ctx->headers, config); |
| } |
| |
| const serf_bucket_type_t serf_bucket_type_request = { |
| "REQUEST", |
| serf_request_read, |
| serf_request_readline, |
| serf_request_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_request_peek, |
| serf_request_destroy, |
| serf_request_read_bucket, |
| serf_default_get_remaining, |
| serf_request_set_config, |
| }; |
| |
| typedef enum incoming_rq_status_t |
| { |
| STATE_INIT, |
| STATE_HEADERS, |
| STATE_PREBODY, |
| STATE_BODY, |
| STATE_TRAILERS, |
| STATE_DONE |
| } incoming_rq_status_t; |
| |
| typedef struct incoming_request_context_t { |
| const char *method; |
| const char *path_raw; |
| int version; |
| |
| serf_bucket_t *stream; |
| serf_bucket_t *headers; |
| serf_bucket_t *body; |
| |
| incoming_rq_status_t state; |
| bool expect_trailers; |
| |
| /* Buffer for accumulating a line from the response. */ |
| serf_linebuf_t linebuf; |
| |
| } incoming_request_context_t; |
| |
| serf_bucket_t *serf_bucket_incoming_request_create( |
| serf_bucket_t *stream, |
| serf_bucket_alloc_t *allocator) |
| { |
| incoming_request_context_t *ctx; |
| |
| ctx = serf_bucket_mem_calloc(allocator, sizeof(*ctx)); |
| |
| ctx->stream = stream; |
| ctx->state = STATE_INIT; |
| ctx->headers = serf_bucket_headers_create(allocator); |
| serf_linebuf_init(&ctx->linebuf); |
| |
| return serf_bucket_create(&serf_bucket_type_incoming_request, |
| allocator, ctx); |
| } |
| |
| static apr_status_t serf_incoming_rq_parse_rqline(serf_bucket_t *bucket) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| const char *spc, *spc2; |
| int res; |
| |
| if (ctx->linebuf.state != SERF_LINEBUF_READY) |
| return APR_SUCCESS; |
| |
| if (ctx->linebuf.used == 0) { |
| return SERF_ERROR_TRUNCATED_STREAM; |
| } |
| |
| /* ### This may need some security review if this is used in production |
| code */ |
| spc = memchr(ctx->linebuf.line, ' ', ctx->linebuf.used); |
| |
| if (spc) |
| ctx->method = serf_bstrmemdup(bucket->allocator, ctx->linebuf.line, |
| spc - ctx->linebuf.line); |
| else |
| return SERF_ERROR_TRUNCATED_STREAM; |
| |
| spc2 = memchr(spc + 1, ' ', ctx->linebuf.used - (ctx->linebuf.line - spc) |
| - 1); |
| |
| if (spc2) |
| ctx->path_raw = serf_bstrmemdup(bucket->allocator, spc + 1, |
| (spc2 - spc-1)); |
| else |
| return SERF_ERROR_TRUNCATED_STREAM; |
| |
| spc2++; |
| /* spc2 should now be of form 'HTTP/1.1' |
| NOTE: Since r1699995 linebuf.line is always NUL terminated string. */ |
| res = apr_date_checkmask(spc2, "HTTP/#.#"); |
| if (!res) { |
| /* Not an HTTP response? Well, at least we won't understand it. */ |
| return SERF_ERROR_TRUNCATED_STREAM; |
| } |
| |
| ctx->version = SERF_HTTP_VERSION(spc2[5] - '0', |
| spc2[7] - '0'); |
| ctx->state++; |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t serf_incoming_rq_parse_headerline(serf_bucket_t *bucket) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| const char *split; |
| |
| if (ctx->linebuf.state != SERF_LINEBUF_READY) |
| return APR_SUCCESS; |
| |
| if (ctx->linebuf.used == 0) { |
| ctx->state++; |
| return APR_SUCCESS; |
| } |
| |
| split = memchr(ctx->linebuf.line, ':', ctx->linebuf.used); |
| |
| serf_bucket_headers_setx(ctx->headers, |
| ctx->linebuf.line, (split - ctx->linebuf.line), |
| TRUE /* copy */, |
| split + 2, |
| ctx->linebuf.used - (split - ctx->linebuf.line) - 2, |
| TRUE /* copy */); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t serf_incoming_rq_wait_for(serf_bucket_t *bucket, |
| incoming_rq_status_t wait_for) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| apr_status_t read_status, status; |
| |
| if (ctx->state == STATE_TRAILERS && wait_for == STATE_BODY) { |
| /* We are done with the body, but not with the request. |
| Can't return EOF yet */ |
| wait_for = STATE_DONE; |
| } |
| |
| while (ctx->state < wait_for) { |
| switch (ctx->state) { |
| case STATE_INIT: |
| read_status = serf_linebuf_fetch(&ctx->linebuf, ctx->stream, |
| SERF_NEWLINE_ANY); |
| if (SERF_BUCKET_READ_ERROR(read_status)) |
| return read_status; |
| |
| status = serf_incoming_rq_parse_rqline(bucket); |
| if (status || read_status) |
| return status ? status : read_status; |
| break; |
| case STATE_HEADERS: |
| case STATE_TRAILERS: |
| read_status = serf_linebuf_fetch(&ctx->linebuf, ctx->stream, |
| SERF_NEWLINE_ANY); |
| if (SERF_BUCKET_READ_ERROR(read_status)) |
| return read_status; |
| |
| status = serf_incoming_rq_parse_headerline(bucket); |
| if (status || read_status) |
| return status ? status : read_status; |
| break; |
| case STATE_PREBODY: |
| /* TODO: Determine the body type.. Wrap bucket if necessary, |
| etc.*/ |
| |
| /* What kind of body do we expect */ |
| { |
| const char *te; |
| |
| ctx->body = ctx->stream; |
| te = serf_bucket_headers_get(ctx->headers, "Transfer-Encoding"); |
| |
| if (te && strcasecmp(te, "chunked") == 0) { |
| ctx->body = serf_bucket_dechunk_create(ctx->stream, |
| bucket->allocator); |
| ctx->expect_trailers = true; |
| } |
| else { |
| const char *cl; |
| |
| cl = serf_bucket_headers_get(ctx->headers, "Content-Length"); |
| |
| if (cl) { |
| apr_uint64_t length; |
| length = apr_strtoi64(cl, NULL, 10); |
| if (errno == ERANGE) { |
| return APR_FROM_OS_ERROR(ERANGE); |
| } |
| ctx->body = serf_bucket_response_body_create( |
| ctx->body, length, bucket->allocator); |
| } |
| } |
| ctx->state++; |
| } |
| break; |
| case STATE_DONE: |
| break; |
| default: |
| return APR_EGENERAL; /* Should never happen */ |
| } |
| } |
| |
| return (ctx->state == STATE_DONE) ? APR_EOF : APR_SUCCESS; |
| } |
| |
| static apr_status_t serf_incoming_rq_read(serf_bucket_t *bucket, |
| apr_size_t requested, |
| const char **data, |
| apr_size_t *len) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_incoming_rq_wait_for(bucket, STATE_BODY); |
| if (status || !ctx->body) { |
| *len = 0; |
| return status ? status : APR_EOF; |
| } |
| |
| status = serf_bucket_read(ctx->body, requested, data, len); |
| if (APR_STATUS_IS_EOF(status) && ctx->expect_trailers) { |
| ctx->state = STATE_TRAILERS; |
| status = APR_SUCCESS; |
| } |
| return status; |
| } |
| |
| static apr_status_t serf_incoming_rq_readline(serf_bucket_t *bucket, int acceptable, |
| int *found, |
| const char **data, apr_size_t *len) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_incoming_rq_wait_for(bucket, STATE_BODY); |
| if (status || !ctx->body) { |
| *found = 0; |
| *len = 0; |
| return status ? status : APR_EOF; |
| } |
| |
| status = serf_bucket_readline(ctx->body, acceptable, found, data, len); |
| if (APR_STATUS_IS_EOF(status) && ctx->expect_trailers) { |
| ctx->state = STATE_TRAILERS; |
| status = APR_SUCCESS; |
| } |
| return status; |
| } |
| |
| static apr_status_t serf_incoming_rq_read_iovec(serf_bucket_t *bucket, |
| apr_size_t requested, |
| int vecs_size, |
| struct iovec *vecs, |
| int *vecs_used) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_incoming_rq_wait_for(bucket, STATE_BODY); |
| if (status || !ctx->body) { |
| *vecs_used = 0; |
| return status ? status : APR_EOF; |
| } |
| |
| status = serf_bucket_read_iovec(ctx->body, requested, vecs_size, |
| vecs, vecs_used); |
| if (APR_STATUS_IS_EOF(status) && ctx->expect_trailers) { |
| ctx->state = STATE_TRAILERS; |
| status = APR_SUCCESS; |
| } |
| return status; |
| } |
| |
| static apr_status_t serf_incoming_rq_peek(serf_bucket_t *bucket, |
| const char **data, |
| apr_size_t *len) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_incoming_rq_wait_for(bucket, STATE_BODY); |
| if (status || !ctx->body) { |
| *len = 0; |
| |
| if (SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| else if (APR_STATUS_IS_EOF(status)) |
| return SERF_ERROR_TRUNCATED_STREAM; |
| |
| return status ? APR_SUCCESS : APR_EOF; |
| } |
| |
| status = serf_bucket_peek(ctx->body, data, len); |
| if (APR_STATUS_IS_EOF(status) && ctx->expect_trailers) { |
| ctx->state = STATE_TRAILERS; |
| status = APR_SUCCESS; |
| } |
| return status; |
| } |
| |
| static void serf_incoming_rq_destroy(serf_bucket_t *bucket) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| |
| if (ctx->method) |
| serf_bucket_mem_free(bucket->allocator, (void*)ctx->method); |
| if (ctx->path_raw) |
| serf_bucket_mem_free(bucket->allocator, (void*)ctx->path_raw); |
| if (ctx->headers) |
| serf_bucket_destroy(ctx->headers); |
| if (ctx->body) |
| serf_bucket_destroy(ctx->body); |
| else if (ctx->stream) |
| serf_bucket_destroy(ctx->stream); |
| |
| serf_default_destroy_and_data(bucket); |
| } |
| |
| apr_status_t serf_bucket_incoming_request_read( |
| serf_bucket_t **headers, |
| const char **method, |
| const char **path, |
| int *http_version, |
| serf_bucket_t *bucket) |
| { |
| incoming_request_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_incoming_rq_wait_for(bucket, STATE_BODY); |
| if (status) { |
| if (headers) |
| *headers = NULL; |
| if (method) |
| *method = NULL; |
| if (path) |
| *path = NULL; |
| if (http_version) |
| *http_version = 0; |
| |
| return status; |
| } |
| |
| if (headers) |
| *headers = ctx->headers; |
| if (method) |
| *method = ctx->method; |
| if (path) |
| *path = ctx->path_raw; |
| if (http_version) |
| *http_version = ctx->version; |
| |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t serf_bucket_incoming_request_wait_for_headers( |
| serf_bucket_t *bucket) |
| { |
| return serf_incoming_rq_wait_for(bucket, STATE_BODY); |
| } |
| |
| |
| const serf_bucket_type_t serf_bucket_type_incoming_request = { |
| "INCOMING-REQUEST", |
| serf_incoming_rq_read, |
| serf_incoming_rq_readline, |
| serf_incoming_rq_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_incoming_rq_peek, |
| serf_incoming_rq_destroy, |
| serf_default_read_bucket, |
| serf_default_get_remaining, |
| serf_default_ignore_config |
| }; |