blob: 9c53aedd6fbdb1b0f1bd1ee62fc8c69abdef4d20 [file] [log] [blame]
/* ====================================================================
* 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
};