| /* ==================================================================== |
| * 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_poll.h> |
| #include <apr_version.h> |
| #include <apr_portable.h> |
| #include <apr_strings.h> |
| |
| #include "serf.h" |
| #include "serf_bucket_util.h" |
| |
| #include "serf_private.h" |
| #include "protocols/fcgi_buckets.h" |
| #include "protocols/fcgi_protocol.h" |
| |
| #define SERF_ERROR_FCGI_RECORD_SIZE_ERROR SERF_ERROR_HTTP2_FRAME_SIZE_ERROR |
| #define SERF_ERROR_FCGI_PROTOCOL_ERROR SERF_ERROR_HTTP2_PROTOCOL_ERROR |
| |
| struct serf_fcgi_protocol_t |
| { |
| serf_connection_t *conn; |
| serf_incoming_t *client; |
| |
| serf_io_baton_t *io; /* Low level connection */ |
| serf_pump_t *pump; |
| |
| apr_pool_t *pool; |
| serf_bucket_alloc_t *allocator; |
| serf_config_t *config; |
| |
| serf_fcgi_processor_t processor; |
| void *processor_baton; |
| |
| serf_bucket_t *read_frame; |
| bool in_frame; |
| |
| serf_fcgi_stream_t *first, *last; |
| |
| bool no_keep_conn; |
| |
| }; |
| |
| static apr_status_t fcgi_cleanup(void *baton) |
| { |
| serf_fcgi_protocol_t *fcgi = baton; |
| |
| fcgi = fcgi; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Implements serf_bucket_prefix_handler_t. |
| Handles PING frames for pings initiated locally */ |
| static apr_status_t fcgi_begin_request(void *baton, |
| serf_bucket_t *bucket, |
| const char *data, |
| apr_size_t len) |
| { |
| serf_fcgi_stream_t *stream = baton; |
| const FCGI_BeginRequestBody *brb; |
| |
| if (len != sizeof(*brb)) |
| return SERF_ERROR_FCGI_RECORD_SIZE_ERROR; |
| |
| brb = (const void*)data; |
| |
| stream->role = (brb->roleB1 << 8) | (brb->roleB0); |
| |
| if (!(brb->flags & FCGI_KEEP_CONN)) |
| stream->fcgi->no_keep_conn = true; |
| |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| /* Implements the serf_bucket_end_of_frame_t callback */ |
| static apr_status_t |
| fcgi_end_of_frame(void *baton, |
| serf_bucket_t *frame) |
| { |
| serf_fcgi_protocol_t *fcgi = baton; |
| |
| SERF_FCGI_assert(fcgi->read_frame == frame); |
| fcgi->read_frame = NULL; |
| fcgi->in_frame = FALSE; |
| fcgi->processor = NULL; |
| fcgi->processor_baton = NULL; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Implements serf_fcgi_processor_t */ |
| static apr_status_t |
| fcgi_bucket_processor(void *baton, |
| serf_fcgi_protocol_t *h2, |
| serf_bucket_t *frame_bucket) |
| { |
| struct iovec vecs[SERF__STD_IOV_COUNT]; |
| int vecs_used; |
| serf_bucket_t *payload = baton; |
| apr_status_t status; |
| |
| status = serf_bucket_read_iovec(payload, SERF_READ_ALL_AVAIL, |
| COUNT_OF(vecs), |
| vecs, &vecs_used); |
| |
| if (APR_STATUS_IS_EOF(status)) |
| { |
| SERF_FCGI_assert(!h2->in_frame && !h2->read_frame); |
| serf_bucket_destroy(payload); |
| } |
| |
| return status; |
| } |
| |
| |
| |
| static apr_status_t fcgi_process(serf_fcgi_protocol_t *fcgi) |
| { |
| while (true) |
| { |
| apr_status_t status; |
| serf_bucket_t *body; |
| |
| if (fcgi->processor) |
| { |
| status = fcgi->processor(fcgi->processor_baton, fcgi, |
| fcgi->read_frame); |
| |
| if (SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| else if (APR_STATUS_IS_EOF(status)) |
| { |
| /* ### frame ended */ |
| SERF_FCGI_assert(fcgi->read_frame == NULL); |
| fcgi->processor = NULL; |
| fcgi->processor_baton = NULL; |
| } |
| else if (fcgi->in_frame) |
| { |
| if (status) |
| return status; |
| else |
| continue; |
| } |
| } |
| else |
| { |
| SERF_FCGI_assert(!fcgi->in_frame); |
| } |
| |
| body = fcgi->read_frame; |
| |
| if (!body) |
| { |
| SERF_FCGI_assert(!fcgi->in_frame); |
| |
| body = serf__bucket_fcgi_unframe_create(fcgi->pump->stream, |
| fcgi->allocator); |
| |
| serf__bucket_fcgi_unframe_set_eof(body, |
| fcgi_end_of_frame, fcgi); |
| |
| serf_bucket_set_config(body, fcgi->config); |
| fcgi->read_frame = body; |
| } |
| |
| if (!fcgi->in_frame) |
| { |
| apr_uint16_t sid; |
| apr_uint16_t frametype; |
| apr_size_t remaining; |
| serf_fcgi_processor_t process_handler = NULL; |
| void *process_baton = NULL; |
| serf_bucket_t *process_bucket = NULL; |
| serf_fcgi_stream_t *stream; |
| |
| status = serf__bucket_fcgi_unframe_read_info(body, &sid, |
| &frametype); |
| |
| if (APR_STATUS_IS_EOF(status)) |
| { |
| /* Entire frame is already read (just header) */ |
| SERF_FCGI_assert(fcgi->read_frame == NULL); |
| SERF_FCGI_assert(!fcgi->in_frame); |
| } |
| else if (status) |
| { |
| SERF_FCGI_assert(fcgi->read_frame != NULL); |
| SERF_FCGI_assert(!fcgi->in_frame); |
| return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS |
| : status; |
| } |
| else |
| { |
| fcgi->in_frame = TRUE; |
| SERF_FCGI_assert(fcgi->read_frame != NULL); |
| } |
| |
| serf__log(LOGLVL_INFO, SERF_LOGCOMP_PROTOCOL, __FILE__, |
| fcgi->config, |
| "Reading 0x%x frame, stream=0x%x\n", |
| frametype, sid); |
| |
| /* If status is EOF then the frame doesn't have/declare a body */ |
| switch (frametype) |
| { |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_BEGIN_REQUEST): |
| stream = serf_fcgi__stream_get(fcgi, sid, false); |
| |
| if (stream) { |
| /* Stream must be new */ |
| return SERF_ERROR_FCGI_PROTOCOL_ERROR; |
| } |
| stream = serf_fcgi__stream_get(fcgi, sid, true); |
| |
| remaining = (apr_size_t)serf_bucket_get_remaining(body); |
| if (remaining != sizeof(FCGI_BeginRequestBody)) { |
| return SERF_ERROR_FCGI_RECORD_SIZE_ERROR; |
| } |
| body = serf_bucket_prefix_create( |
| body, |
| sizeof(FCGI_BeginRequestBody), |
| fcgi_begin_request, stream, |
| fcgi->allocator); |
| |
| /* Just reading will handle this frame now*/ |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_ABORT_REQUEST): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_END_REQUEST): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_PARAMS): |
| stream = serf_fcgi__stream_get(fcgi, sid, false); |
| if (!stream) { |
| return SERF_ERROR_FCGI_PROTOCOL_ERROR; |
| } |
| |
| body = serf_fcgi__stream_handle_params(stream, body, |
| fcgi->config, |
| fcgi->allocator); |
| |
| if (body) { |
| /* We will take care of discarding */ |
| process_bucket = body; |
| } |
| else |
| { |
| /* The stream wants to handle the reading itself */ |
| process_handler = serf_fcgi__stream_processor; |
| process_baton = stream; |
| } |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_STDIN): |
| stream = serf_fcgi__stream_get(fcgi, sid, false); |
| if (!stream) { |
| return SERF_ERROR_FCGI_PROTOCOL_ERROR; |
| } |
| |
| body = serf_fcgi__stream_handle_stdin(stream, body, |
| fcgi->config, |
| fcgi->allocator); |
| |
| if (body) { |
| /* We will take care of discarding */ |
| process_bucket = body; |
| } |
| else |
| { |
| /* The stream wants to handle the reading itself */ |
| process_handler = serf_fcgi__stream_processor; |
| process_baton = stream; |
| } |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_STDOUT): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_STDERR): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_DATA): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_GET_VALUES): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_GET_VALUES_RESULT): |
| process_bucket = body; |
| break; |
| case FCGI_FRAMETYPE(FCGI_V1, FCGI_UNKNOWN_TYPE): |
| process_bucket = body; |
| break; |
| default: |
| process_bucket = body; |
| }; |
| |
| if (body) |
| serf_bucket_set_config(body, fcgi->config); |
| |
| SERF_FCGI_assert(fcgi->processor == NULL); |
| |
| if (process_handler) |
| { |
| fcgi->processor = process_handler; |
| fcgi->processor_baton = process_baton; |
| } |
| else |
| { |
| SERF_FCGI_assert(process_bucket != NULL); |
| fcgi->processor = fcgi_bucket_processor; |
| fcgi->processor_baton = process_bucket; |
| } |
| } |
| } /* while(TRUE) */ |
| } |
| |
| static apr_status_t fcgi_read(serf_fcgi_protocol_t *fcgi) |
| { |
| apr_status_t status = fcgi_process(fcgi); |
| |
| if (!status || SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t serf_fcgi__enqueue_frame(serf_fcgi_protocol_t *fcgi, |
| serf_bucket_t *frame, |
| bool flush) |
| { |
| return serf_pump__add_output(fcgi->pump, frame, flush); |
| } |
| |
| static apr_status_t fcgi_write(serf_fcgi_protocol_t *fcgi) |
| { |
| apr_status_t status; |
| |
| if (fcgi->client) |
| status = serf__incoming_client_flush(fcgi->client, true); |
| else |
| status = serf__connection_flush(fcgi->conn, true); |
| |
| if (APR_STATUS_IS_EAGAIN(status)) |
| return APR_SUCCESS; |
| else if (status) |
| return status; |
| |
| /* Probably nothing to write. */ |
| serf_io__set_pollset_dirty(fcgi->io); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t fcgi_hangup(serf_fcgi_protocol_t *fcgi) |
| { |
| return APR_ENOTIMPL; |
| } |
| |
| static void fcgi_teardown(serf_fcgi_protocol_t *fcgi) |
| { |
| |
| } |
| |
| serf_fcgi_stream_t * |
| serf_fcgi__stream_get(serf_fcgi_protocol_t *fcgi, |
| apr_uint16_t streamid, |
| bool create) |
| { |
| serf_fcgi_stream_t *stream; |
| |
| if (streamid == 0) |
| return NULL; |
| |
| for (stream = fcgi->first; stream; stream = stream->next) |
| { |
| if (stream->streamid == streamid) |
| return stream; |
| } |
| |
| if (create) |
| { |
| stream = serf_fcgi__stream_create(fcgi, streamid, fcgi->allocator); |
| |
| if (fcgi->first) |
| { |
| stream->next = fcgi->first; |
| fcgi->first->prev = stream; |
| fcgi->first = stream; |
| } |
| else |
| fcgi->last = fcgi->first = stream; |
| |
| return stream; |
| } |
| return NULL; |
| } |
| |
| void serf_fcgi__close_stream(serf_fcgi_protocol_t *fcgi, |
| serf_fcgi_stream_t *stream) |
| { |
| if (!stream->prev) |
| fcgi->first = stream->next; |
| else |
| stream->prev->next = stream; |
| |
| if (stream->next) |
| stream->next->prev = stream->prev; |
| else |
| fcgi->last = stream->prev; |
| |
| fcgi->first = fcgi->last = NULL; |
| |
| serf_fcgi__stream_destroy(stream); |
| } |
| |
| apr_status_t serf_fcgi__setup_incoming_request( |
| serf_incoming_request_t **in_request, |
| serf_incoming_request_setup_t *req_setup, |
| void **req_setup_baton, |
| serf_fcgi_protocol_t *fcgi) |
| { |
| if (!fcgi->client) |
| return SERF_ERROR_FCGI_PROTOCOL_ERROR; |
| |
| *in_request = serf__incoming_request_create(fcgi->client); |
| *req_setup = fcgi->client->req_setup; |
| *req_setup_baton = fcgi->client->req_setup_baton; |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| /* --------------- connection support --------------- */ |
| static apr_status_t fcgi_outgoing_read(serf_connection_t *conn) |
| { |
| serf_fcgi_protocol_t *fcgi = conn->protocol_baton; |
| |
| return fcgi_read(fcgi); |
| } |
| |
| static apr_status_t fcgi_outgoing_write(serf_connection_t *conn) |
| { |
| serf_fcgi_protocol_t *fcgi = conn->protocol_baton; |
| |
| return fcgi_write(fcgi); |
| } |
| |
| static apr_status_t fcgi_outgoing_hangup(serf_connection_t *conn) |
| { |
| serf_fcgi_protocol_t *fcgi = conn->protocol_baton; |
| |
| return fcgi_hangup(fcgi); |
| } |
| |
| static void fcgi_outgoing_teardown(serf_connection_t *conn) |
| { |
| serf_fcgi_protocol_t *fcgi = conn->protocol_baton; |
| |
| fcgi_teardown(fcgi); |
| } |
| |
| void serf__fcgi_protocol_init(serf_connection_t *conn) |
| { |
| serf_fcgi_protocol_t *fcgi; |
| apr_pool_t *protocol_pool; |
| |
| apr_pool_create(&protocol_pool, conn->pool); |
| |
| fcgi = apr_pcalloc(protocol_pool, sizeof(*fcgi)); |
| fcgi->pool = protocol_pool; |
| fcgi->conn = conn; |
| fcgi->io = &conn->io; |
| fcgi->pump = &conn->pump; |
| fcgi->allocator = conn->allocator; |
| fcgi->config = conn->config; |
| |
| apr_pool_cleanup_register(protocol_pool, fcgi, fcgi_cleanup, |
| apr_pool_cleanup_null); |
| |
| conn->perform_read = fcgi_outgoing_read; |
| conn->perform_write = fcgi_outgoing_write; |
| conn->perform_hangup = fcgi_outgoing_hangup; |
| conn->perform_teardown = fcgi_outgoing_teardown; |
| conn->protocol_baton = fcgi; |
| |
| /* Disable HTTP/1.1 guessing that affects writability */ |
| conn->probable_keepalive_limit = 0; |
| conn->max_outstanding_requests = 0; |
| } |
| |
| /* --------------- connection support --------------- */ |
| static apr_status_t fcgi_server_read(serf_incoming_t *client) |
| { |
| serf_fcgi_protocol_t *fcgi = client->protocol_baton; |
| |
| return fcgi_read(fcgi); |
| } |
| |
| static apr_status_t fcgi_server_write(serf_incoming_t *client) |
| { |
| serf_fcgi_protocol_t *fcgi = client->protocol_baton; |
| |
| return fcgi_write(fcgi); |
| } |
| |
| static apr_status_t fcgi_server_hangup(serf_incoming_t *client) |
| { |
| serf_fcgi_protocol_t *fcgi = client->protocol_baton; |
| |
| return fcgi_hangup(fcgi); |
| } |
| |
| static void fcgi_server_teardown(serf_incoming_t *client) |
| { |
| serf_fcgi_protocol_t *fcgi = client->protocol_baton; |
| |
| fcgi_teardown(fcgi); |
| } |
| |
| void serf__fcgi_protocol_init_server(serf_incoming_t *client) |
| { |
| serf_fcgi_protocol_t *fcgi; |
| apr_pool_t *protocol_pool; |
| |
| apr_pool_create(&protocol_pool, client->pool); |
| |
| fcgi = apr_pcalloc(protocol_pool, sizeof(*fcgi)); |
| fcgi->pool = protocol_pool; |
| fcgi->client = client; |
| fcgi->io = &client->io; |
| fcgi->pump = &client->pump; |
| fcgi->allocator = client->allocator; |
| fcgi->config = client->config; |
| |
| apr_pool_cleanup_register(protocol_pool, fcgi, fcgi_cleanup, |
| apr_pool_cleanup_null); |
| |
| client->perform_read = fcgi_server_read; |
| client->perform_write = fcgi_server_write; |
| client->perform_hangup = fcgi_server_hangup; |
| client->perform_teardown = fcgi_server_teardown; |
| client->protocol_baton = fcgi; |
| } |
| |