Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

758 lines (600 sloc) 19.374 kb
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Cherokee
*
* Authors:
* Alvaro Lopez Ortega <alvaro@alobbs.com>
*
* Copyright (C) 2001-2010 Alvaro Lopez Ortega
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "common-internal.h"
#include <fcntl.h>
#include "handler_fcgi.h"
#include "header.h"
#include "connection-protected.h"
#include "util.h"
#include "thread.h"
#include "source_interpreter.h"
#include "bogotime.h"
#include "error_log.h"
#include "fastcgi.h"
#define POST_PACKAGE_LEN 32600
#define ENTRIES "fcgi,handler"
#define set_env(cgi,key,val,len) \
set_env_pair (cgi, key, sizeof(key)-1, val, len)
static void set_env_pair (cherokee_handler_cgi_base_t *cgi_base,
const char *key, int key_len,
const char *val, int val_len);
/* Plug-in initialization
*/
CGI_LIB_INIT (fcgi, http_all_methods);
/* Methods implementation
*/
static ret_t
process_package (cherokee_handler_fcgi_t *hdl, cherokee_buffer_t *inbuf, cherokee_buffer_t *outbuf)
{
FCGI_Header *header;
cuint_t len;
char *data;
cuint_t type;
cuint_t id;
cuint_t padding;
/* Is there enough information?
*/
if (inbuf->len < sizeof(FCGI_Header))
return ret_ok;
/* At least there is a header
*/
header = (FCGI_Header *)inbuf->buf;
if (header->version != 1) {
cherokee_buffer_print_debug (inbuf, -1);
LOG_ERROR_S (CHEROKEE_ERROR_HANDLER_FCGI_VERSION);
return ret_error;
}
if (header->type != FCGI_STDERR &&
header->type != FCGI_STDOUT &&
header->type != FCGI_END_REQUEST) {
cherokee_buffer_print_debug (inbuf, -1);
LOG_ERROR_S (CHEROKEE_ERROR_HANDLER_FCGI_PARSING);
return ret_error;
}
/* Read the header
*/
type = header->type;
padding = header->paddingLength;
id = (header->requestIdB0 | (header->requestIdB1 << 8));
len = (header->contentLengthB0 | (header->contentLengthB1 << 8));
data = inbuf->buf + FCGI_HEADER_LEN;
/* printf ("have %d, hdr=%d exp_len=%d pad=%d\n", inbuf->len, FCGI_HEADER_LEN, len, padding); */
/* Is the package complete?
*/
if (len + padding > inbuf->len - FCGI_HEADER_LEN) {
/* printf ("Incomplete: %d < %d\n", len + padding, inbuf->len - FCGI_HEADER_LEN); */
return ret_ok;
}
/* It has received the full package content
*/
switch (type) {
case FCGI_STDERR:
/* printf ("READ:STDERR (%d): %s", len, data?data:""); */
LOG_ERROR (CHEROKEE_ERROR_HANDLER_FCGI_STDERR, data);
/* Debug mode */
if (SOURCE_INT(hdl->src_ref)->debug) {
PRINT_MSG ("%.*s\n", len, data);
}
break;
case FCGI_STDOUT:
/* printf ("READ:STDOUT eof=%d: %d", HDL_CGI_BASE(hdl)->got_eof, len); */
cherokee_buffer_add (outbuf, data, len);
break;
case FCGI_END_REQUEST:
/* printf ("READ:END"); */
HDL_CGI_BASE(hdl)->got_eof = true;
break;
default:
SHOULDNT_HAPPEN;
}
cherokee_buffer_move_to_begin (inbuf, len + FCGI_HEADER_LEN + padding);
/* printf ("- FCGI left %d\n", inbuf->len); */
return ret_eagain;
}
static ret_t
process_buffer (cherokee_handler_fcgi_t *hdl, cherokee_buffer_t *inbuf, cherokee_buffer_t *outbuf)
{
ret_t ret;
do {
ret = process_package (hdl, inbuf, outbuf);
} while (ret == ret_eagain);
if (ret == ret_ok) {
if (cherokee_buffer_is_empty (outbuf))
return (HDL_CGI_BASE(hdl)->got_eof) ? ret_eof : ret_eagain;
}
return ret;
}
static ret_t
read_from_fcgi (cherokee_handler_cgi_base_t *cgi, cherokee_buffer_t *buffer)
{
ret_t ret;
size_t read = 0;
cherokee_handler_fcgi_t *fcgi = HDL_FCGI(cgi);
ret = cherokee_socket_bufread (&fcgi->socket, &fcgi->write_buffer, DEFAULT_READ_SIZE, &read);
switch (ret) {
case ret_eagain:
ret = cherokee_thread_deactive_to_polling (HANDLER_THREAD(cgi), HANDLER_CONN(cgi),
fcgi->socket.socket, FDPOLL_MODE_READ,
false);
if (unlikely (ret != ret_ok)) {
cgi->got_eof = true;
return ret_error;
}
return ret_eagain;
case ret_ok:
ret = process_buffer (fcgi, &fcgi->write_buffer, buffer);
TRACE (ENTRIES, "%d bytes read, buffer.len %d\n", read, buffer->len);
if ((ret == ret_ok) && cgi->got_eof && (buffer->len > 0)) {
return ret_eof_have_data;
}
return ret;
case ret_eof:
case ret_error:
cgi->got_eof = true;
return ret;
default:
RET_UNKNOWN(ret);
}
SHOULDNT_HAPPEN;
return ret_error;
}
static ret_t
props_free (cherokee_handler_fcgi_props_t *props)
{
if (props->balancer != NULL)
cherokee_balancer_free (props->balancer);
return cherokee_handler_cgi_base_props_free (PROP_CGI_BASE(props));
}
ret_t
cherokee_handler_fcgi_configure (cherokee_config_node_t *conf,
cherokee_server_t *srv,
cherokee_module_props_t **_props)
{
ret_t ret;
cherokee_list_t *i;
cherokee_handler_fcgi_props_t *props;
/* Instance a new property object
*/
if (*_props == NULL) {
CHEROKEE_NEW_STRUCT (n, handler_fcgi_props);
cherokee_handler_cgi_base_props_init_base (PROP_CGI_BASE(n),
MODULE_PROPS_FREE(props_free));
INIT_LIST_HEAD (&n->server_list);
n->balancer = NULL;
*_props = MODULE_PROPS(n);
}
props = PROP_FCGI(*_props);
/* Parse the configuration tree
*/
cherokee_config_node_foreach (i, conf) {
cherokee_config_node_t *subconf = CONFIG_NODE(i);
if (equal_buf_str (&subconf->key, "balancer")) {
ret = cherokee_balancer_instance (&subconf->val, subconf, srv, &props->balancer);
if (ret != ret_ok) return ret;
}
}
/* Init base class
*/
ret = cherokee_handler_cgi_base_configure (conf, srv, _props);
if (ret != ret_ok) return ret;
/* Final checks
*/
if (props->balancer == NULL) {
LOG_CRITICAL_S (CHEROKEE_ERROR_HANDLER_FCGI_BALANCER);
return ret_error;
}
return ret_ok;
}
ret_t
cherokee_handler_fcgi_new (cherokee_handler_t **hdl, void *cnt, cherokee_module_props_t *props)
{
CHEROKEE_NEW_STRUCT (n, handler_fcgi);
/* Init the base class
*/
cherokee_handler_cgi_base_init (HDL_CGI_BASE(n), cnt, PLUGIN_INFO_HANDLER_PTR(fcgi),
HANDLER_PROPS(props), set_env_pair, read_from_fcgi);
/* Virtual methods
*/
MODULE(n)->init = (handler_func_init_t) cherokee_handler_fcgi_init;
MODULE(n)->free = (module_func_free_t) cherokee_handler_fcgi_free;
HANDLER(n)->read_post = (handler_func_read_post_t) cherokee_handler_fcgi_read_post;
/* Virtual methods: implemented by handler_cgi_base
*/
HANDLER(n)->add_headers = (handler_func_add_headers_t) cherokee_handler_cgi_base_add_headers;
HANDLER(n)->step = (handler_func_step_t) cherokee_handler_cgi_base_step;
/* Properties
*/
n->post_phase = fcgi_post_phase_read;
n->src_ref = NULL;
cherokee_socket_init (&n->socket);
cherokee_buffer_init (&n->write_buffer);
cherokee_buffer_ensure_size (&n->write_buffer, 512);
/* Return the object
*/
*hdl = HANDLER(n);
return ret_ok;
}
ret_t
cherokee_handler_fcgi_free (cherokee_handler_fcgi_t *hdl)
{
TRACE (ENTRIES, "fcgi handler free: %p\n", hdl);
cherokee_socket_close (&hdl->socket);
cherokee_socket_mrproper (&hdl->socket);
cherokee_buffer_mrproper (&hdl->write_buffer);
return cherokee_handler_cgi_base_free (HDL_CGI_BASE(hdl));
}
static void
fcgi_build_header (FCGI_Header *hdr, cuchar_t type, cushort_t request_id, cuint_t content_length, cuchar_t padding)
{
hdr->version = FCGI_VERSION_1;
hdr->type = type;
hdr->requestIdB0 = (cuchar_t) request_id;
hdr->requestIdB1 = (cuchar_t) (request_id >> 8) & 0xff;
hdr->contentLengthB0 = (cuchar_t) (content_length % 256);
hdr->contentLengthB1 = (cuchar_t) (content_length / 256);
hdr->paddingLength = padding;
hdr->reserved = 0;
}
static void
fcgi_build_request_body (FCGI_BeginRequestRecord *request)
{
request->body.roleB0 = FCGI_RESPONDER;
request->body.roleB1 = 0;
request->body.flags = 0;
request->body.reserved[0] = 0;
request->body.reserved[1] = 0;
request->body.reserved[2] = 0;
request->body.reserved[3] = 0;
request->body.reserved[4] = 0;
}
static void
set_env_pair (cherokee_handler_cgi_base_t *cgi_base,
const char *key, int key_len,
const char *val, int val_len)
{
int len;
FCGI_BeginRequestRecord request;
cherokee_handler_fcgi_t *hdl = HDL_FCGI(cgi_base);
cherokee_buffer_t *buf = &hdl->write_buffer;
#ifdef TRACE_ENABLED
cherokee_buffer_t *tmp = &HANDLER_THREAD(cgi_base)->tmp_buf2;
cherokee_buffer_clean (tmp);
cherokee_buffer_add (tmp, key, key_len);
cherokee_buffer_add_str (tmp, " = \"");
cherokee_buffer_add (tmp, val, val_len);
cherokee_buffer_add_str (tmp, "\"\n");
TRACE (ENTRIES, "%s", tmp->buf);
#endif
len = key_len + val_len;
len += key_len > 127 ? 4 : 1;
len += val_len > 127 ? 4 : 1;
fcgi_build_header (&request.header, FCGI_PARAMS, 1, len, 0);
cherokee_buffer_ensure_size (buf, buf->len + sizeof(FCGI_Header) + key_len + val_len);
cherokee_buffer_add (buf, (void *)&request.header, sizeof(FCGI_Header));
if (key_len <= 127) {
buf->buf[buf->len++] = key_len;
} else {
buf->buf[buf->len++] = ((key_len >> 24) & 0xff) | 0x80;
buf->buf[buf->len++] = (key_len >> 16) & 0xff;
buf->buf[buf->len++] = (key_len >> 8) & 0xff;
buf->buf[buf->len++] = (key_len >> 0) & 0xff;
}
if (val_len <= 127) {
buf->buf[buf->len++] = val_len;
} else {
buf->buf[buf->len++] = ((val_len >> 24) & 0xff) | 0x80;
buf->buf[buf->len++] = (val_len >> 16) & 0xff;
buf->buf[buf->len++] = (val_len >> 8) & 0xff;
buf->buf[buf->len++] = (val_len >> 0) & 0xff;
}
cherokee_buffer_add (buf, key, key_len);
cherokee_buffer_add (buf, val, val_len);
}
static ret_t
add_extra_fcgi_env (cherokee_handler_fcgi_t *hdl, cuint_t *last_header_offset)
{
cherokee_handler_cgi_base_t *cgi_base = HDL_CGI_BASE(hdl);
cherokee_buffer_t buffer = CHEROKEE_BUF_INIT;
cherokee_connection_t *conn = HANDLER_CONN(hdl);
/* POST management
*/
if (http_method_with_input (conn->header.method)) {
if (conn->post.encoding == post_enc_regular) {
cherokee_buffer_add_ullong10 (&buffer, conn->post.len);
set_env (cgi_base, "CONTENT_LENGTH", buffer.buf, buffer.len);
} else if (conn->post.encoding == post_enc_chunked) {
TRACE (ENTRIES",post", "Setting Chunked Post: %s flag\n", "retransmit");
set_env (cgi_base, "CONTENT_TRANSFER_ENCODING", "chunked", 7);
conn->post.chunked.retransmit = true;
}
}
/* Add PATH_TRANSLATED only it there is pathinfo
*/
#if 0
if (! cherokee_buffer_is_empty (&conn->pathinfo)) {
cherokee_buffer_add_buffer (&buffer, &conn->local_directory);
cherokee_buffer_add_buffer (&buffer, &conn->pathinfo);
set_env (cgi_base, "PATH_TRANSLATED", buffer.buf, buffer.len);
TRACE (ENTRIES, "PATH_TRANSLATED '%s'\n", cgi_base->executable.buf);
}
#endif
/* The last one
*/
*last_header_offset = hdl->write_buffer.len;
set_env (cgi_base, "SCRIPT_FILENAME",
cgi_base->executable.buf,
cgi_base->executable.len);
TRACE (ENTRIES, "SCRIPT_FILENAME '%s'\n", cgi_base->executable.buf);
cherokee_buffer_mrproper (&buffer);
return ret_ok;
}
static void
fixup_padding (cherokee_buffer_t *buf, cuint_t last_header_offset)
{
cuint_t rest;
cuint_t pad;
static char padding[8] = {0, 0, 0, 0, 0, 0, 0, 0};
FCGI_Header *last_header;
if (buf->len <= 0)
return;
last_header = (FCGI_Header *) (buf->buf + last_header_offset);
rest = buf->len % 8;
pad = 8 - rest;
if (rest == 0)
return;
last_header->paddingLength = pad;
cherokee_buffer_ensure_size (buf, buf->len + pad);
cherokee_buffer_add (buf, padding, pad);
}
static void
add_empty_packet (cherokee_handler_fcgi_t *hdl, cuint_t type)
{
FCGI_BeginRequestRecord request;
fcgi_build_header (&request.header, type, 1, 0, 0);
cherokee_buffer_add (&hdl->write_buffer, (void *)&request.header, sizeof(FCGI_Header));
TRACE (ENTRIES, "empty packet type=%d, len=%d\n", type, hdl->write_buffer.len);
}
static ret_t
build_header (cherokee_handler_fcgi_t *hdl, cherokee_buffer_t *buffer)
{
FCGI_BeginRequestRecord request;
cuint_t last_header_offset;
cherokee_connection_t *conn = HANDLER_CONN(hdl);
cherokee_buffer_clean (buffer);
/* FCGI_BEGIN_REQUEST
*/
fcgi_build_header (&request.header, FCGI_BEGIN_REQUEST, 1, sizeof(request.body), 0);
fcgi_build_request_body (&request);
cherokee_buffer_add (buffer, (void *)&request, sizeof(FCGI_BeginRequestRecord));
TRACE (ENTRIES, "Added FCGI_BEGIN_REQUEST, len=%d\n", buffer->len);
/* Add enviroment variables
*/
cherokee_handler_cgi_base_build_envp (HDL_CGI_BASE(hdl), conn);
add_extra_fcgi_env (hdl, &last_header_offset);
fixup_padding (buffer, last_header_offset);
/* There are no more parameters
*/
add_empty_packet (hdl, FCGI_PARAMS);
/* No POST?
*/
if ((! http_method_with_input (conn->header.method)) || (! conn->post.has_info)) {
TRACE (ENTRIES",post", "Post: %s\n", "has no post");
add_empty_packet (hdl, FCGI_STDIN);
}
TRACE (ENTRIES, "Added FCGI_PARAMS, len=%d\n", buffer->len);
return ret_ok;
}
static ret_t
connect_to_server (cherokee_handler_fcgi_t *hdl)
{
ret_t ret;
cherokee_connection_t *conn = HANDLER_CONN(hdl);
cherokee_handler_fcgi_props_t *props = HANDLER_FCGI_PROPS(hdl);
/* Get a reference to the target host
*/
if (hdl->src_ref == NULL) {
ret = cherokee_balancer_dispatch (props->balancer, conn, &hdl->src_ref);
if (ret != ret_ok)
return ret;
}
/* Try to connect
*/
if (hdl->src_ref->type == source_host) {
ret = cherokee_source_connect_polling (hdl->src_ref, &hdl->socket, conn);
if ((ret == ret_deny) || (ret == ret_error))
{
cherokee_balancer_report_fail (props->balancer, conn, hdl->src_ref);
}
} else {
ret = cherokee_source_interpreter_connect_polling (SOURCE_INT(hdl->src_ref),
&hdl->socket, conn);
}
return ret;
}
static ret_t
do_send (cherokee_handler_fcgi_t *hdl,
cherokee_buffer_t *buffer)
{
ret_t ret;
size_t written = 0;
cherokee_connection_t *conn = HANDLER_CONN(hdl);
ret = cherokee_socket_bufwrite (&hdl->socket, buffer, &written);
switch (ret) {
case ret_ok:
break;
case ret_eagain:
if (written > 0)
break;
return ret_eagain;
default:
conn->error_code = http_bad_gateway;
return ret_error;
}
cherokee_buffer_move_to_begin (buffer, written);
TRACE (ENTRIES, "sent=%d, remaining=%d\n", written, buffer->len);
return ret_ok;
}
static ret_t
send_post (cherokee_handler_fcgi_t *hdl,
cherokee_buffer_t *buf)
{
ret_t ret;
int prev_buf_len;
cherokee_connection_t *conn = HANDLER_CONN(hdl);
static FCGI_Header empty_header = {0,0,0,0,0,0,0,0};
switch (hdl->post_phase) {
case fcgi_post_phase_read:
TRACE (ENTRIES",post", "Post %s\n", "read");
/* Add space for the header, it'll filled out later on..
*/
if (cherokee_buffer_is_empty (buf)) {
cherokee_buffer_add (buf, (const char *)&empty_header, sizeof (FCGI_Header));
}
/* Take a chunck of post
*/
ret = cherokee_post_read (&conn->post, &conn->socket, buf);
if (ret != ret_ok) {
return ret;
}
TRACE (ENTRIES",post", "Post buffer.len %d\n", buf->len);
/* Did something, increase timeout
*/
cherokee_connection_update_timeout (conn);
/* Complete the header
*/
if (buf->len > sizeof(FCGI_Header)) {
fcgi_build_header ((FCGI_Header *)buf->buf, FCGI_STDIN, 1,
buf->len - sizeof(FCGI_Header), 0);
}
/* Close STDIN if it was the last chunck
*/
if (cherokee_post_read_finished (&conn->post)) {
add_empty_packet (hdl, FCGI_STDIN);
}
hdl->post_phase = fcgi_post_phase_write;
case fcgi_post_phase_write:
TRACE (ENTRIES",post", "Post write, buf.len=%d (header len %d)\n", buf->len, sizeof(FCGI_Header));
if (! cherokee_buffer_is_empty (buf)) {
prev_buf_len = buf->len;
ret = do_send (hdl, buf);
switch (ret) {
case ret_ok:
/* Did something, increase timeout
*/
if (buf->len < prev_buf_len) {
cherokee_connection_update_timeout (conn);
}
/* Block on back-end write
*/
if (buf->len > 0) {
return ret_deny;
}
break;
case ret_eagain:
return ret_eagain;
case ret_eof:
case ret_error:
return ret_error;
default:
RET_UNKNOWN(ret);
return ret_error;
}
}
/* Next iteration
*/
if (! cherokee_buffer_is_empty (buf)) {
cherokee_thread_deactive_to_polling (HANDLER_THREAD(hdl),
HANDLER_CONN(hdl),
hdl->socket.socket,
FDPOLL_MODE_WRITE, false);
return ret_deny;
}
if (! cherokee_post_read_finished (&conn->post)) {
hdl->post_phase = fcgi_post_phase_read;
return ret_eagain;
}
TRACE (ENTRIES",post", "Post %s\n", "finished");
return ret_ok;
default:
SHOULDNT_HAPPEN;
}
return ret_error;
}
ret_t
cherokee_handler_fcgi_init (cherokee_handler_fcgi_t *hdl)
{
ret_t ret;
cherokee_connection_t *conn = HANDLER_CONN(hdl);
cherokee_handler_cgi_base_props_t *props = HANDLER_CGI_BASE_PROPS(hdl);
switch (HDL_CGI_BASE(hdl)->init_phase) {
case hcgi_phase_build_headers:
TRACE (ENTRIES, "Init: %s\n", "begins");
/* Extracts PATH_INFO and filename from request uri
*/
ret = cherokee_handler_cgi_base_extract_path (HDL_CGI_BASE(hdl), props->check_file);
if (unlikely (ret < ret_ok)) return ret;
/* Build the headers
*/
ret = build_header (hdl, &hdl->write_buffer);
if (unlikely (ret != ret_ok)) return ret;
HDL_CGI_BASE(hdl)->init_phase = hcgi_phase_connect;
case hcgi_phase_connect:
TRACE (ENTRIES, "Init: %s\n", "connect");
/* Connect
*/
ret = connect_to_server (hdl);
switch (ret) {
case ret_ok:
break;
case ret_eagain:
return ret_eagain;
case ret_deny:
conn->error_code = http_gateway_timeout;
return ret_error;
default:
conn->error_code = http_service_unavailable;
return ret_error;
}
HDL_CGI_BASE(hdl)->init_phase = hcgi_phase_send_headers;
case hcgi_phase_send_headers:
TRACE (ENTRIES, "Init: %s\n", "send_headers");
/* Send the header
*/
ret = do_send (hdl, &hdl->write_buffer);
if (ret != ret_ok) {
return ret;
}
if (! cherokee_buffer_is_empty (&hdl->write_buffer)) {
return ret_eagain;
}
break;
}
TRACE (ENTRIES, "Init %s\n", "finishes");
cherokee_buffer_clean (&hdl->write_buffer);
return ret_ok;
}
ret_t
cherokee_handler_fcgi_read_post (cherokee_handler_fcgi_t *hdl)
{
return send_post (hdl, &hdl->write_buffer);
}
Jump to Line
Something went wrong with that request. Please try again.