| /* ==================================================================== |
| * 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 <stdlib.h> |
| |
| #include <apr_pools.h> |
| |
| #include "serf.h" |
| #include "serf_bucket_util.h" |
| #include "serf_private.h" |
| |
| #include "protocols/http2_buckets.h" |
| |
| /* https://tools.ietf.org/html/rfc7540#section-4.1 */ |
| #define FRAME_PREFIX_SIZE 9 |
| |
| typedef struct http2_unframe_context_t |
| { |
| serf_bucket_t *stream; |
| apr_size_t max_payload_size; |
| |
| apr_size_t prefix_remaining; |
| |
| serf_bucket_end_of_frame_t end_of_frame; |
| void *end_of_frame_baton; |
| |
| /* These fields are only set after prefix_remaining is 0 */ |
| apr_size_t payload_remaining; /* 0 <= payload_length < 2^24 */ |
| apr_int32_t stream_id; /* 0 <= stream_id < 2^31 */ |
| unsigned char frame_type; |
| unsigned char flags; |
| |
| unsigned char buffer[FRAME_PREFIX_SIZE]; |
| } http2_unframe_context_t; |
| |
| serf_bucket_t * |
| serf__bucket_http2_unframe_create(serf_bucket_t *stream, |
| apr_size_t max_payload_size, |
| serf_bucket_alloc_t *allocator) |
| { |
| http2_unframe_context_t *ctx; |
| |
| ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); |
| ctx->stream = stream; |
| ctx->max_payload_size = max_payload_size; |
| ctx->prefix_remaining = sizeof(ctx->buffer); |
| ctx->end_of_frame = NULL; |
| |
| return serf_bucket_create(&serf_bucket_type__http2_unframe, allocator, ctx); |
| } |
| |
| void |
| serf__bucket_http2_unframe_set_eof(serf_bucket_t *bucket, |
| serf_bucket_end_of_frame_t end_of_frame, |
| void *end_of_frame_baton) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| |
| ctx->end_of_frame = end_of_frame; |
| ctx->end_of_frame_baton = end_of_frame_baton; |
| } |
| |
| apr_status_t |
| serf__bucket_http2_unframe_read_info(serf_bucket_t *bucket, |
| apr_int32_t *stream_id, |
| unsigned char *frame_type, |
| unsigned char *flags) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| const char *data; |
| apr_size_t len; |
| apr_status_t status; |
| const unsigned char *header; |
| |
| if (ctx->prefix_remaining == 0) { |
| if (stream_id) |
| *stream_id = ctx->stream_id; |
| if (frame_type) |
| *frame_type = ctx->frame_type; |
| if (flags) |
| *flags = ctx->flags; |
| |
| return APR_SUCCESS; |
| } |
| |
| do |
| { |
| status = serf_bucket_read(ctx->stream, ctx->prefix_remaining, &data, &len); |
| if (SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| else if (!status && !len) |
| return SERF_ERROR_EMPTY_READ; |
| |
| if (len < FRAME_PREFIX_SIZE) { |
| memcpy(ctx->buffer + FRAME_PREFIX_SIZE - ctx->prefix_remaining, |
| data, len); |
| |
| ctx->prefix_remaining -= len; |
| header = ctx->buffer; |
| } |
| else { |
| header = (const void *)data; |
| ctx->prefix_remaining = 0; |
| } |
| } while (!status && ctx->prefix_remaining > 0); |
| |
| if (ctx->prefix_remaining == 0) { |
| apr_size_t payload_length = (header[0] << 16) |
| | (header[1] << 8) |
| | (header[2]); |
| ctx->frame_type = header[3]; |
| ctx->flags = header[4]; |
| /* Highest bit of stream_id MUST be ignored */ |
| ctx->stream_id = ((header[5] & 0x7F) << 24) |
| | (header[6] << 16) |
| | (header[7] << 8) |
| | (header[8]); |
| |
| ctx->payload_remaining = payload_length; |
| |
| /* Fill output arguments if necessary */ |
| if (stream_id) |
| *stream_id = ctx->stream_id; |
| if (frame_type) |
| *frame_type = ctx->frame_type; |
| if (flags) |
| *flags = ctx->flags; |
| |
| /* https://tools.ietf.org/html/rfc7540#section-4.2 |
| An endpoint MUST send an error code of FRAME_SIZE_ERROR if a |
| frame exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, |
| exceeds any limit defined for the frame type, or is too small |
| to contain mandatory frame data. |
| */ |
| if (ctx->max_payload_size < payload_length) |
| { |
| if (payload_length == 0x485454 && ctx->frame_type == 0x50 |
| && ctx->flags == 0x2F) |
| { |
| /* We found "HTTP/" instead of an actual frame. This |
| is clearly above the initial max payload size of 16384, |
| which applies before we negotiate a bigger size. |
| |
| We found a HTTP/1.1 server that didn't understand our |
| HTTP2 prefix "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" |
| */ |
| |
| return SERF_ERROR_HTTP2_PROTOCOL_ERROR; |
| } |
| |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| |
| if (ctx->payload_remaining == 0) |
| status = APR_EOF; |
| else if (APR_STATUS_IS_EOF(status)) |
| status = SERF_ERROR_TRUNCATED_STREAM; |
| |
| /* If we hava a zero-length frame we have to call the eof callback |
| now, as the read operations will just shortcut to APR_EOF */ |
| if (ctx->payload_remaining == 0 && ctx->end_of_frame) { |
| apr_status_t cb_status; |
| |
| cb_status = (*ctx->end_of_frame)(ctx->end_of_frame_baton, |
| bucket); |
| |
| if (SERF_BUCKET_READ_ERROR(cb_status)) |
| status = cb_status; |
| } |
| } |
| else if (APR_STATUS_IS_EOF(status)) { |
| /* Reading frame failed because we couldn't read the header. Report |
| a read failure instead of semi-success */ |
| if (ctx->prefix_remaining == FRAME_PREFIX_SIZE) |
| status = SERF_ERROR_EMPTY_STREAM; |
| else |
| status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_unframe_read(serf_bucket_t *bucket, |
| apr_size_t requested, |
| const char **data, |
| apr_size_t *len) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); |
| |
| if (status) { |
| *len = 0; |
| return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : status; |
| } |
| |
| if (ctx->payload_remaining == 0) { |
| *len = 0; |
| return APR_EOF; |
| } |
| |
| if (requested > ctx->payload_remaining) |
| requested = ctx->payload_remaining; |
| |
| status = serf_bucket_read(ctx->stream, requested, data, len); |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| ctx->payload_remaining -= *len; |
| |
| if (ctx->payload_remaining == 0) { |
| if (ctx->end_of_frame) |
| status = (*ctx->end_of_frame)(ctx->end_of_frame_baton, |
| bucket); |
| |
| if (!SERF_BUCKET_READ_ERROR(status)) |
| status = APR_EOF; |
| } |
| else if (APR_STATUS_IS_EOF(status)) |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_unframe_read_iovec(serf_bucket_t *bucket, |
| apr_size_t requested, |
| int vecs_size, |
| struct iovec *vecs, |
| int *vecs_used) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); |
| |
| if (status) { |
| *vecs_used = 0; |
| return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : status; |
| } |
| |
| if (ctx->payload_remaining == 0) { |
| *vecs_used = 0; |
| return APR_EOF; |
| } |
| |
| if (requested > ctx->payload_remaining) |
| requested = ctx->payload_remaining; |
| |
| status = serf_bucket_read_iovec(ctx->stream, requested, |
| vecs_size, vecs, vecs_used); |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| int i; |
| apr_size_t len = 0; |
| |
| for (i = 0; i < *vecs_used; i++) |
| len += vecs[i].iov_len; |
| |
| ctx->payload_remaining -= len; |
| |
| if (ctx->payload_remaining == 0) { |
| if (ctx->end_of_frame) |
| status = (*ctx->end_of_frame)(ctx->end_of_frame_baton, |
| bucket); |
| |
| if (!SERF_BUCKET_READ_ERROR(status)) |
| status = APR_EOF; |
| } |
| else if (APR_STATUS_IS_EOF(status)) |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_unframe_peek(serf_bucket_t *bucket, |
| const char **data, |
| apr_size_t *len) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); |
| |
| if (status) { |
| *len = 0; |
| return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : status; |
| } |
| |
| status = serf_bucket_peek(ctx->stream, data, len); |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| if (*len > ctx->payload_remaining) |
| *len = ctx->payload_remaining; |
| } |
| |
| return status; |
| } |
| |
| static apr_uint64_t |
| serf_http2_unframe_get_remaining(serf_bucket_t *bucket) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); |
| |
| if (status) |
| return SERF_LENGTH_UNKNOWN; |
| |
| return ctx->payload_remaining; |
| } |
| |
| const serf_bucket_type_t serf_bucket_type__http2_unframe = { |
| "H2-UNFRAME", |
| serf_http2_unframe_read, |
| serf_default_readline, |
| serf_http2_unframe_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_http2_unframe_peek, |
| serf_default_destroy_and_data, |
| serf_default_read_bucket, |
| serf_http2_unframe_get_remaining, |
| serf_default_ignore_config |
| }; |
| |
| typedef struct http2_unpad_context_t |
| { |
| serf_bucket_t *stream; |
| apr_size_t payload_remaining; |
| apr_size_t pad_remaining; |
| apr_size_t pad_length; |
| char padsize_read; |
| } http2_unpad_context_t; |
| |
| serf_bucket_t * |
| serf__bucket_http2_unpad_create(serf_bucket_t *stream, |
| serf_bucket_alloc_t *allocator) |
| { |
| http2_unpad_context_t *ctx; |
| |
| ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); |
| ctx->stream = stream; |
| ctx->padsize_read = FALSE; |
| |
| return serf_bucket_create(&serf_bucket_type__http2_unpad, allocator, ctx); |
| } |
| |
| static apr_status_t |
| serf_http2_unpad_read_padsize(serf_bucket_t *bucket) |
| { |
| http2_unpad_context_t *ctx = bucket->data; |
| apr_status_t status; |
| const char *data; |
| apr_size_t len; |
| |
| if (ctx->padsize_read) |
| return APR_SUCCESS; |
| |
| status = serf_bucket_read(ctx->stream, 1, &data, &len); |
| if (!SERF_BUCKET_READ_ERROR(status) && len > 0) { |
| apr_int64_t remaining; |
| |
| ctx->pad_length = *(unsigned char *)data; |
| ctx->pad_remaining = ctx->pad_length; |
| ctx->padsize_read = TRUE; |
| |
| /* We call get_remaining() *after* reading from ctx->stream, |
| to allow the framing above us to be read before we call this */ |
| remaining = serf_bucket_get_remaining(ctx->stream); |
| |
| if (remaining == SERF_LENGTH_UNKNOWN |
| || remaining > APR_SIZE_MAX) |
| return APR_EGENERAL; /* Can't calculate padding size */ |
| |
| /* http://tools.ietf.org/html/rfc7540#section-6.1 |
| If the length of the padding is the length of the |
| frame payload or greater, the recipient MUST treat this as a |
| connection error (Section 5.4.1) of type PROTOCOL_ERROR. |
| |
| The frame payload includes the length byte, so when remaining |
| is 0, that isn't a protocol error */ |
| if (remaining < ctx->pad_length) |
| return SERF_ERROR_HTTP2_PROTOCOL_ERROR; |
| |
| ctx->payload_remaining = (apr_size_t)remaining - ctx->pad_length; |
| } |
| else if (APR_STATUS_IS_EOF(status)) |
| status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| else if (!status) |
| status = APR_EAGAIN; |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_unpad_read(serf_bucket_t *bucket, |
| apr_size_t requested, |
| const char **data, |
| apr_size_t *len) |
| { |
| http2_unpad_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_http2_unpad_read_padsize(bucket); |
| |
| if (status) { |
| *len = 0; |
| return status; |
| } |
| else if (ctx->payload_remaining == 0 |
| && ctx->pad_remaining == 0) |
| { |
| *len = 0; |
| return APR_EOF; |
| } |
| |
| |
| if (requested >= ctx->payload_remaining) |
| requested = ctx->payload_remaining + ctx->pad_remaining; |
| |
| status = serf_bucket_read(ctx->stream, requested, data, len); |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| if (*len < ctx->payload_remaining) |
| ctx->payload_remaining -= *len; |
| else |
| { |
| ctx->pad_remaining -= (*len - ctx->payload_remaining); |
| *len = ctx->payload_remaining; |
| ctx->payload_remaining = 0; |
| |
| if (ctx->pad_remaining == 0) |
| status = APR_EOF; |
| } |
| |
| if (APR_STATUS_IS_EOF(status) |
| && (ctx->pad_remaining != 0 || ctx->payload_remaining != 0)) |
| { |
| status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_unpad_read_iovec(serf_bucket_t *bucket, |
| apr_size_t requested, |
| int vecs_size, |
| struct iovec *vecs, |
| int *vecs_used) |
| { |
| http2_unpad_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_http2_unpad_read_padsize(bucket); |
| |
| if (status) { |
| *vecs_used = 0; |
| return status; |
| } |
| else if (ctx->payload_remaining == 0 |
| && ctx->pad_remaining == 0) |
| { |
| *vecs_used = 0; |
| return APR_EOF; |
| } |
| |
| if (requested > ctx->payload_remaining) |
| requested = ctx->payload_remaining + ctx->pad_remaining; |
| |
| status = serf_bucket_read_iovec(ctx->stream, requested, |
| vecs_size, vecs, vecs_used); |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| int i; |
| apr_size_t total = 0; |
| |
| for (i = 0; i < *vecs_used; i++) |
| total += vecs[i].iov_len; |
| |
| if (total < ctx->payload_remaining) |
| ctx->payload_remaining -= total; |
| else |
| { |
| apr_size_t padread = (total - ctx->payload_remaining); |
| ctx->pad_remaining -= padread; |
| ctx->payload_remaining = 0; |
| |
| /* Remove padding from returned result? */ |
| while (padread && *vecs_used) |
| { |
| struct iovec *cv = &vecs[*vecs_used - 1]; |
| |
| if (cv->iov_len <= padread) |
| { |
| padread -= cv->iov_len; |
| (*vecs_used)--; |
| } |
| else |
| { |
| cv->iov_len -= padread; |
| break; |
| } |
| } |
| |
| if (ctx->pad_remaining == 0) |
| status = APR_EOF; |
| } |
| |
| if (APR_STATUS_IS_EOF(status) |
| && (ctx->pad_remaining != 0 || ctx->payload_remaining != 0)) |
| { |
| status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_unpad_peek(serf_bucket_t *bucket, |
| const char **data, |
| apr_size_t *len) |
| { |
| http2_unpad_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_http2_unpad_read_padsize(bucket); |
| |
| if (status) { |
| *len = 0; |
| return status; |
| } |
| |
| status = serf_bucket_peek(ctx->stream, data, len); |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| if (*len > ctx->payload_remaining) |
| *len = ctx->payload_remaining; |
| } |
| |
| return status; |
| } |
| |
| static void |
| serf_http2_unpad_destroy(serf_bucket_t *bucket) |
| { |
| http2_unpad_context_t *ctx = bucket->data; |
| |
| serf_bucket_destroy(ctx->stream); |
| |
| serf_default_destroy_and_data(bucket); |
| } |
| |
| static apr_uint64_t |
| serf_http2_unpad_get_remaining(serf_bucket_t *bucket) |
| { |
| http2_unframe_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = serf_http2_unpad_read_padsize(bucket); |
| |
| if (status) |
| return SERF_LENGTH_UNKNOWN; |
| |
| return ctx->payload_remaining; |
| } |
| |
| const serf_bucket_type_t serf_bucket_type__http2_unpad = { |
| "H2-UNPAD", |
| serf_http2_unpad_read, |
| serf_default_readline, |
| serf_http2_unpad_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_http2_unpad_peek, |
| serf_http2_unpad_destroy, |
| serf_default_read_bucket, |
| serf_http2_unpad_get_remaining, |
| serf_default_ignore_config |
| }; |
| |
| /* ==================================================================== */ |
| |
| typedef struct serf_http2_frame_context_t { |
| serf_bucket_t *stream; |
| serf_bucket_alloc_t *alloc; |
| serf_bucket_t *chunk; |
| apr_size_t bytes_remaining; |
| apr_size_t max_payload_size; |
| |
| apr_int32_t stream_id; |
| |
| unsigned char frametype; |
| unsigned char flags; |
| char created_frame; |
| |
| apr_int32_t *p_stream_id; |
| void *stream_id_baton; |
| void(*stream_id_alloc)(void *baton, apr_int32_t *stream_id); |
| |
| serf_config_t *config; |
| |
| } serf_http2_frame_context_t; |
| |
| serf_bucket_t * |
| serf__bucket_http2_frame_create(serf_bucket_t *stream, |
| unsigned char frame_type, |
| unsigned char flags, |
| apr_int32_t *stream_id, |
| void(*stream_id_alloc)( |
| void *baton, |
| apr_int32_t *stream_id), |
| void *stream_id_baton, |
| apr_uint32_t max_payload_size, |
| serf_bucket_alloc_t *alloc) |
| { |
| serf_http2_frame_context_t *ctx = serf_bucket_mem_alloc(alloc, |
| sizeof(*ctx)); |
| |
| ctx->alloc = alloc; |
| ctx->stream = stream; |
| ctx->chunk = serf_bucket_aggregate_create(alloc); |
| ctx->max_payload_size = max_payload_size; |
| ctx->frametype = frame_type; |
| ctx->flags = flags; |
| |
| if (max_payload_size > 0xFFFFFF) |
| max_payload_size = 0xFFFFFF; |
| |
| if (!stream_id_alloc || (stream_id && *stream_id >= 0)) |
| { |
| /* Avoid all alloc handling; we know the final id */ |
| ctx->stream_id = stream_id ? *stream_id : 0; |
| ctx->p_stream_id = &ctx->stream_id; |
| ctx->stream_id_alloc = NULL; |
| ctx->stream_id_baton = NULL; |
| } |
| else |
| { |
| /* Delay creating the id until we really need it. |
| |
| Using a higher stream number before a lower version in communication |
| closes the lower number directly (as 'unused') */ |
| |
| ctx->stream_id = -1; |
| ctx->p_stream_id = stream_id; |
| ctx->stream_id_alloc = stream_id_alloc; |
| ctx->stream_id_baton = stream_id_baton; |
| } |
| |
| ctx->config = NULL; |
| ctx->created_frame = FALSE; |
| |
| return serf_bucket_create(&serf_bucket_type__http2_frame, alloc, ctx); |
| } |
| |
| static apr_status_t |
| http2_prepare_frame(serf_bucket_t *bucket) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| int vecs_used; |
| apr_uint64_t payload_remaining; |
| |
| if (ctx->created_frame) |
| return APR_SUCCESS; |
| |
| /* How long will this frame be? */ |
| if (!ctx->stream) |
| payload_remaining = 0; |
| else |
| payload_remaining = serf_bucket_get_remaining(ctx->stream); |
| |
| if (payload_remaining != SERF_LENGTH_UNKNOWN |
| && payload_remaining > ctx->max_payload_size) |
| { |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| else if (payload_remaining != SERF_LENGTH_UNKNOWN) { |
| if (ctx->stream) |
| serf_bucket_aggregate_append(ctx->chunk, ctx->stream); |
| |
| ctx->stream = NULL; /* Now managed by aggregate */ |
| } |
| else { |
| /* Our payload doesn't know how long it is. Our only option |
| now is to create the actual data */ |
| struct iovec vecs[SERF__STD_IOV_COUNT]; |
| apr_status_t status; |
| |
| status = serf_bucket_read_iovec(ctx->stream, ctx->max_payload_size, |
| COUNT_OF(vecs), vecs, &vecs_used); |
| |
| if (SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| else if (APR_STATUS_IS_EOF(status)) |
| { |
| /* OK, we got everything, let's put the data at the start of the |
| aggregate. */ |
| serf_bucket_aggregate_append_iovec(ctx->chunk, vecs, vecs_used); |
| |
| /* Obtain the size now , to avoid problems when the bucket |
| doesn't know that it has nothing remaining*/ |
| payload_remaining = serf_bucket_get_remaining(ctx->chunk); |
| |
| /* Just add the stream behind the iovecs. This keeps the chunks |
| available exactly until they are no longer necessary */ |
| serf_bucket_aggregate_append(ctx->chunk, ctx->stream); |
| ctx->stream = NULL; /* Managed by aggregate */ |
| |
| if (payload_remaining == SERF_LENGTH_UNKNOWN) |
| { |
| /* Should never happen: |
| Aggregate with only iovecs should know size */ |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| } |
| else |
| { |
| /* Auch... worst case scenario, we have to copy the data. Luckily |
| we have an absolute limit after which we may error out */ |
| apr_size_t total = 0; |
| char *data = serf_bucket_mem_alloc(bucket->allocator, |
| ctx->max_payload_size); |
| |
| serf__copy_iovec(data, &total, vecs, vecs_used); |
| |
| while (!APR_STATUS_IS_EOF(status) |
| && total < ctx->max_payload_size) { |
| apr_size_t read; |
| status = serf_bucket_read_iovec(ctx->stream, |
| ctx->max_payload_size - total + 1, |
| COUNT_OF(vecs), vecs, &vecs_used); |
| |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| serf_bucket_mem_free(bucket->allocator, data); |
| return status; |
| } |
| |
| serf__copy_iovec(data, &read, vecs, vecs_used); |
| total += read; |
| |
| if (status && !APR_STATUS_IS_EOF(status)) { |
| /* Checkpoint what we got now... |
| |
| Next time this function is called the buffer is read first and |
| then continued from the original stream */ |
| serf_bucket_t *new_stream; |
| new_stream = serf_bucket_aggregate_create(bucket->allocator); |
| |
| serf_bucket_aggregate_append( |
| new_stream, |
| serf_bucket_simple_own_create(data, total, bucket->allocator)); |
| |
| serf_bucket_aggregate_append(new_stream, ctx->stream); |
| ctx->stream = new_stream; |
| |
| return status; |
| } |
| } |
| |
| if (total > ctx->max_payload_size) { |
| /* The chunk is at least 1 byte bigger then allowed */ |
| serf_bucket_mem_free(bucket->allocator, data); |
| |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| else { |
| /* Ok, we have what we need in our buffer */ |
| serf_bucket_aggregate_append( |
| ctx->chunk, |
| serf_bucket_simple_own_create(data, total, |
| bucket->allocator)); |
| payload_remaining = total; |
| |
| /* And we no longer need stream */ |
| serf_bucket_destroy(ctx->stream); |
| ctx->stream = NULL; |
| } |
| } |
| } |
| |
| /* Ok, now we can construct the frame */ |
| ctx->created_frame = TRUE; |
| { |
| unsigned char frame[FRAME_PREFIX_SIZE]; |
| |
| /* Allocate the streamid if there isn't one. |
| Once the streamid hits the wire it automatically closes all |
| unused identifiers < this value. |
| */ |
| if (ctx->stream_id < 0 && ctx->stream_id_alloc) |
| { |
| ctx->stream_id_alloc(ctx->stream_id_baton, ctx->p_stream_id); |
| ctx->stream_id = *ctx->p_stream_id; |
| } |
| |
| frame[0] = (payload_remaining >> 16) & 0xFF; |
| frame[1] = (payload_remaining >> 8) & 0xFF; |
| frame[2] = payload_remaining & 0xFF; |
| frame[3] = ctx->frametype; |
| frame[4] = ctx->flags; |
| frame[5] = ((apr_uint32_t)ctx->stream_id >> 24) & 0x7F; |
| frame[6] = ((apr_uint32_t)ctx->stream_id >> 16) & 0xFF; |
| frame[7] = ((apr_uint32_t)ctx->stream_id >> 8) & 0xFF; |
| frame[8] = ctx->stream_id & 0xFF; |
| |
| /* Put the frame before the data */ |
| serf_bucket_aggregate_prepend(ctx->chunk, |
| serf_bucket_simple_copy_create((const char *)&frame, |
| FRAME_PREFIX_SIZE, |
| ctx->alloc)); |
| |
| /* And set the amount of data that we verify will be read */ |
| ctx->bytes_remaining = (apr_size_t)payload_remaining |
| + FRAME_PREFIX_SIZE; |
| } |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t |
| serf_http2_frame_read(serf_bucket_t *bucket, |
| apr_size_t requested, |
| const char **data, |
| apr_size_t *len) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = http2_prepare_frame(bucket); |
| if (status) |
| return status; |
| |
| status = serf_bucket_read(ctx->chunk, requested, data, len); |
| |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| if (*len > ctx->bytes_remaining) |
| { |
| /* Frame payload resized after the header was written */ |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| ctx->bytes_remaining -= *len; |
| } |
| |
| if (APR_STATUS_IS_EOF(status)) { |
| if (ctx->bytes_remaining > 0) { |
| /* Frame payload resized after the header was written */ |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_frame_read_iovec(serf_bucket_t *bucket, |
| apr_size_t requested, |
| int vecs_size, |
| struct iovec *vecs, |
| int *vecs_used) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = http2_prepare_frame(bucket); |
| if (status) |
| return status; |
| |
| status = serf_bucket_read_iovec(ctx->chunk, requested, vecs_size, vecs, |
| vecs_used); |
| |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| apr_size_t len = 0; |
| int i; |
| |
| for (i = 0; i < *vecs_used; i++) |
| len += vecs[i].iov_len; |
| |
| if (len > ctx->bytes_remaining) |
| { |
| /* Frame resized after the header was written */ |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| ctx->bytes_remaining -= len; |
| } |
| |
| if (APR_STATUS_IS_EOF(status)) { |
| if (ctx->bytes_remaining > 0) { |
| /* Frame payload resized after the header was written */ |
| return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; |
| } |
| } |
| |
| return status; |
| } |
| |
| static apr_status_t |
| serf_http2_frame_peek(serf_bucket_t *bucket, |
| const char **data, |
| apr_size_t *len) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| apr_status_t status; |
| |
| status = http2_prepare_frame(bucket); |
| if (status) { |
| *len = 0; |
| return APR_SUCCESS; |
| } |
| |
| return serf_bucket_peek(ctx->chunk, data, len); |
| } |
| |
| static apr_uint64_t |
| serf_http2_frame_get_remaining(serf_bucket_t *bucket) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| |
| if (!ctx->created_frame) |
| return SERF_LENGTH_UNKNOWN; |
| else |
| return ctx->bytes_remaining; |
| } |
| |
| static apr_status_t |
| serf_http2_frame_set_config(serf_bucket_t *bucket, |
| serf_config_t *config) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| ctx->config = config; |
| |
| if (ctx->stream) |
| return serf_bucket_set_config(ctx->stream, config); |
| |
| return APR_SUCCESS; |
| } |
| |
| static void |
| serf_http2_frame_destroy(serf_bucket_t *bucket) |
| { |
| serf_http2_frame_context_t *ctx = bucket->data; |
| |
| if (ctx->stream) |
| serf_bucket_destroy(ctx->stream); |
| |
| serf_bucket_destroy(ctx->chunk); |
| |
| serf_default_destroy_and_data(bucket); |
| } |
| |
| const serf_bucket_type_t serf_bucket_type__http2_frame = |
| { |
| "H2-FRAME", |
| serf_http2_frame_read, |
| serf_default_readline, |
| serf_http2_frame_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_http2_frame_peek, |
| serf_http2_frame_destroy, |
| serf_default_read_bucket, |
| serf_http2_frame_get_remaining, |
| serf_http2_frame_set_config |
| }; |
| |