blob: 1572ef3896312591add100edcd48c8fddb4a0cd9 [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 <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;
}