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

1309 lines (1069 sloc) 34.133 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-2011 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 "handler_cgi_base.h"
#include "socket.h"
#include "util.h"
#include "connection-protected.h"
#include "server-protected.h"
#include "header-protected.h"
#include "handler_file.h"
#define ENTRIES "cgibase"
#define set_env(cgi,key,val,len) \
set_env_pair (cgi, key, sizeof(key)-1, val, len)
static cherokee_handler_file_props_t handler_file_props;
ret_t
cherokee_handler_cgi_base_init (cherokee_handler_cgi_base_t *cgi,
cherokee_connection_t *conn,
cherokee_plugin_info_handler_t *info,
cherokee_handler_props_t *props,
cherokee_handler_cgi_base_add_env_pair_t add_env_pair,
cherokee_handler_cgi_base_read_from_cgi_t read_from_cgi)
{
/* Init the base class object
*/
cherokee_handler_init_base (HANDLER(cgi), conn, props, info);
/* Init to default values
*/
cgi->init_phase = hcgi_phase_build_headers;
cgi->content_length = 0;
cgi->got_eof = false;
cgi->file_handler = NULL;
cherokee_buffer_init (&cgi->xsendfile);
cherokee_buffer_init (&cgi->executable);
cherokee_buffer_init (&cgi->data);
cherokee_buffer_ensure_size (&cgi->data, 2*1024);
/* Virtual methods
*/
cgi->add_env_pair = add_env_pair;
cgi->read_from_cgi = read_from_cgi;
/* Read the properties
*/
HANDLER(cgi)->support = hsupport_nothing;
return ret_ok;
}
typedef struct {
cherokee_list_t entry;
cherokee_buffer_t env;
cherokee_buffer_t val;
} env_item_t;
static env_item_t *
env_item_new (cherokee_buffer_t *key, cherokee_buffer_t *val)
{
env_item_t *n;
n = malloc (sizeof (env_item_t));
if (unlikely (n == NULL)) {
return NULL;
}
INIT_LIST_HEAD (&n->entry);
cherokee_buffer_init (&n->env);
cherokee_buffer_init (&n->val);
cherokee_buffer_add_buffer(&n->env, key);
cherokee_buffer_add_buffer(&n->val, val);
return n;
}
static void
env_item_free (void *p)
{
env_item_t *n = (env_item_t *)p;
cherokee_buffer_mrproper (&n->env);
cherokee_buffer_mrproper (&n->val);
free (p);
}
ret_t
cherokee_handler_cgi_base_props_init_base (cherokee_handler_cgi_base_props_t *props, module_func_props_free_t free_func)
{
return cherokee_handler_props_init_base (HANDLER_PROPS(props), free_func);
}
ret_t
cherokee_handler_cgi_base_props_free (cherokee_handler_cgi_base_props_t *props)
{
cherokee_list_t *i, *tmp;
cherokee_buffer_mrproper (&props->script_alias);
cherokee_x_real_ip_mrproper (&props->x_real_ip);
list_for_each_safe (i, tmp, &props->system_env) {
env_item_free (i);
}
return cherokee_handler_props_free_base (HANDLER_PROPS(props));
}
ret_t
cherokee_handler_cgi_base_configure (cherokee_config_node_t *conf, cherokee_server_t *srv, cherokee_module_props_t **_props)
{
ret_t ret;
cherokee_list_t *i, *j;
cherokee_handler_cgi_base_props_t *props;
UNUSED(srv);
/* Sanity check: This class is pure virtual,
* it shouldn't allocate memory here.
* Check that the object space has been already instanced by its father.
*/
if (*_props == NULL) {
SHOULDNT_HAPPEN;
return ret_ok;
}
/* Init
*/
props = PROP_CGI_BASE(*_props);
INIT_LIST_HEAD (&props->system_env);
cherokee_buffer_init (&props->script_alias);
cherokee_x_real_ip_init (&props->x_real_ip);
props->is_error_handler = true;
props->change_user = false;
props->check_file = true;
props->allow_xsendfile = false;
props->pass_req_headers = true;
/* Parse the configuration tree
*/
cherokee_config_node_foreach (i, conf) {
cherokee_config_node_t *subconf = CONFIG_NODE(i);
if (equal_buf_str (&subconf->key, "script_alias")) {
ret = cherokee_buffer_add_buffer (&props->script_alias, &subconf->val);
if (ret != ret_ok)
return ret;
} else if (equal_buf_str (&subconf->key, "env")) {
cherokee_config_node_foreach (j, subconf) {
env_item_t *env;
cherokee_config_node_t *subconf2 = CONFIG_NODE(j);
env = env_item_new (&subconf2->key, &subconf2->val);
if (env == NULL)
return ret_error;
cherokee_list_add_tail (LIST(env), &props->system_env);
}
} else if (equal_buf_str (&subconf->key, "error_handler")) {
ret = cherokee_atob (subconf->val.buf, &props->is_error_handler);
if (ret != ret_ok) return ret;
} else if (equal_buf_str (&subconf->key, "change_user")) {
ret = cherokee_atob (subconf->val.buf, &props->change_user);
if (ret != ret_ok) return ret;
} else if (equal_buf_str (&subconf->key, "check_file")) {
ret = cherokee_atob (subconf->val.buf, &props->check_file);
if (ret != ret_ok) return ret;
} else if (equal_buf_str (&subconf->key, "xsendfile")) {
ret = cherokee_atob (subconf->val.buf, &props->allow_xsendfile);
if (ret != ret_ok) return ret;
} else if (equal_buf_str (&subconf->key, "pass_req_headers")) {
ret = cherokee_atob (subconf->val.buf, &props->pass_req_headers);
if (ret != ret_ok) return ret;
}
}
ret = cherokee_x_real_ip_configure (&props->x_real_ip, conf);
if (ret != ret_ok) {
return ret_error;
}
return ret_ok;
}
ret_t
cherokee_handler_cgi_base_free (cherokee_handler_cgi_base_t *cgi)
{
if (cgi->file_handler) {
cherokee_handler_free (cgi->file_handler);
}
cherokee_buffer_mrproper (&cgi->data);
cherokee_buffer_mrproper (&cgi->executable);
cherokee_buffer_mrproper (&cgi->xsendfile);
return ret_ok;
}
#ifdef _WIN32
static void
add_win32_systemroot_env (cherokee_handler_cgi_base_t *cgi,
cherokee_handler_cgi_base_add_env_pair_t set_env_pair)
{
char *root;
root = getenv("SYSTEMROOT");
if (!root)
return;
set_env (cgi, "SYSTEMROOT", root, strlen(root));
}
#endif
ret_t
cherokee_handler_cgi_base_build_basic_env (
cherokee_handler_cgi_base_t *cgi,
cherokee_handler_cgi_base_add_env_pair_t set_env_pair,
cherokee_connection_t *conn,
cherokee_buffer_t *tmp)
{
int re;
ret_t ret;
cherokee_boolean_t remote_addr_set;
char *p = NULL;
cuint_t p_len = 0;
cherokee_bind_t *bind = CONN_BIND(HANDLER_CONN(cgi));
cherokee_handler_cgi_base_props_t *cgi_props = HANDLER_CGI_BASE_PROPS(cgi);
static char *env_PATH = NULL;
static int env_PATH_len = 0;
char remote_ip[CHE_INET_ADDRSTRLEN+1];
CHEROKEE_TEMP(temp, 32);
/* Set the basic variables
*/
set_env (cgi, "SERVER_SOFTWARE",
bind->server_string.buf,
bind->server_string.len);
set_env (cgi, "SERVER_SIGNATURE", "<address>Cherokee Web Server</address>", 38);
set_env (cgi, "GATEWAY_INTERFACE", "CGI/1.1", 7);
/* $PATH
*/
if (unlikely (env_PATH == NULL)) {
env_PATH = getenv("PATH");
if (env_PATH == NULL) {
env_PATH = "/bin:/usr/bin:/sbin:/usr/sbin";
}
env_PATH_len = strlen (env_PATH);
}
set_env (cgi, "PATH", env_PATH, env_PATH_len);
/* Document Root:
*/
if (CONN_VSRV(conn)->evhost) {
set_env (cgi, "DOCUMENT_ROOT",
conn->local_directory.buf,
conn->local_directory.len);
} else {
set_env (cgi, "DOCUMENT_ROOT",
CONN_VSRV(conn)->root.buf,
CONN_VSRV(conn)->root.len);
}
/* REMOTE_(ADDR/PORT): X-Real-IP
*/
remote_addr_set = false;
if (cgi_props->x_real_ip.enabled)
{
/* The request has a X-REAL-IP entry */
cherokee_header_get_known (&conn->header, header_x_real_ip, &p, &p_len);
/* The request has a X-Forwared-For entry */
if (p == NULL) {
cherokee_header_get_known (&conn->header, header_x_forwarded_for, &p, &p_len);
}
if (p != NULL)
{
/* The remote host is allowed to send it */
ret = cherokee_x_real_ip_is_allowed (&cgi_props->x_real_ip, &conn->socket);
if (ret == ret_ok) {
cuint_t i;
const char *port_end = NULL;
const char *colon = NULL;
const char *end = NULL;
for (i=0; i < p_len; i++) {
if ((p[i] == ',') && (end == NULL)) {
end = &p[i];
while ((end > p) && ((*end == ',')||(*end == ' '))) {
end--;
}
end++;
break;
}
if (p[i] == ':') {
colon = &p[i];
break;
}
}
if (end == NULL) {
end = p + p_len;
}
if (colon != NULL) {
port_end = colon + 1;
while ((*port_end >= '0') && (*port_end <= '9')) {
port_end++;
}
set_env (cgi, "REMOTE_ADDR", p, colon - p);
set_env (cgi, "REMOTE_PORT", colon+1, port_end - (colon+1));
} else {
set_env (cgi, "REMOTE_ADDR", p, end - p);
}
remote_addr_set = true;
}
}
}
/* REMOTE_(ADDR/PORT): socket origin
*/
if (! remote_addr_set) {
/* REMOTE_ADDR */
memset (remote_ip, 0, sizeof(remote_ip));
cherokee_socket_ntop (&conn->socket, remote_ip, sizeof(remote_ip)-1);
set_env (cgi, "REMOTE_ADDR", remote_ip, strlen(remote_ip));
/* REMOTE_PORT */
re = snprintf (temp, temp_size, "%d", SOCKET_SIN_PORT(&conn->socket));
if (re > 0) {
set_env (cgi, "REMOTE_PORT", temp, re);
}
}
/* HTTP_HOST and SERVER_NAME. The difference between them is that
* HTTP_HOST can include the «:PORT» text, and SERVER_NAME only
* the name
*/
cherokee_header_copy_known (&conn->header, header_host, tmp);
if (! cherokee_buffer_is_empty(tmp)) {
set_env (cgi, "HTTP_HOST", tmp->buf, tmp->len);
p = strchr (tmp->buf, ':');
if (p != NULL) {
set_env (cgi, "SERVER_NAME", tmp->buf, p - tmp->buf);
} else {
set_env (cgi, "SERVER_NAME", tmp->buf, tmp->len);
}
} else {
ret = cherokee_gethostname (tmp);
if (ret == ret_ok) {
set_env (cgi, "SERVER_NAME", tmp->buf, tmp->len);
} else {
LOG_WARNING_S (CHEROKEE_ERROR_HANDLER_CGI_GET_HOSTNAME);
}
}
/* Content-Type
*/
cherokee_buffer_clean (tmp);
ret = cherokee_header_copy_known (&conn->header, header_content_type, tmp);
if (ret == ret_ok) {
set_env (cgi, "CONTENT_TYPE", tmp->buf, tmp->len);
}
/* SYSTEMROOT environment for Win32
*/
#ifdef _WIN32
add_win32_systemroot_env (cgi, set_env_pair);
#endif
/* Query string
*/
if (conn->query_string.len > 0)
set_env (cgi, "QUERY_STRING", conn->query_string.buf, conn->query_string.len);
else
set_env (cgi, "QUERY_STRING", "", 0);
/* HTTP protocol version
*/
ret = cherokee_http_version_to_string (conn->header.version, (const char **) &p, &p_len);
if (ret >= ret_ok)
set_env (cgi, "SERVER_PROTOCOL", p, p_len);
/* Set the method
*/
ret = cherokee_http_method_to_string (conn->header.method, (const char **) &p, &p_len);
if (ret >= ret_ok)
set_env (cgi, "REQUEST_METHOD", p, p_len);
/* Remote user
*/
if (conn->validator && !cherokee_buffer_is_empty (&conn->validator->user)) {
/* Only set when user authenticated (bug #467) */
set_env (cgi, "REMOTE_USER", conn->validator->user.buf, conn->validator->user.len);
}
/* Set PATH_INFO
*/
if (! cherokee_buffer_is_empty (&conn->pathinfo))
set_env (cgi, "PATH_INFO", conn->pathinfo.buf, conn->pathinfo.len);
else
set_env (cgi, "PATH_INFO", "", 0);
/* Set REQUEST_URI:
*
* Use request + query_string unless the connections has been
* redirected from common and targets a full path index file.
*/
cherokee_buffer_clean (tmp);
if (conn->options & conn_op_root_index) {
cherokee_header_copy_request_w_args (&conn->header, tmp);
}
else {
if (! cherokee_buffer_is_empty (&conn->userdir)) {
cherokee_buffer_add_str (tmp, "/~");
cherokee_buffer_add_buffer (tmp, &conn->userdir);
}
if (! cherokee_buffer_is_empty (&conn->request_original)) {
cherokee_buffer_add_buffer (tmp, &conn->request_original);
if (! cherokee_buffer_is_empty (&conn->query_string_original)) {
cherokee_buffer_add_char (tmp, '?');
cherokee_buffer_add_buffer (tmp, &conn->query_string_original);
}
} else {
cherokee_buffer_add_buffer (tmp, &conn->request);
if (! cherokee_buffer_is_empty (&conn->query_string)) {
cherokee_buffer_add_char (tmp, '?');
cherokee_buffer_add_buffer (tmp, &conn->query_string);
}
}
}
set_env (cgi, "REQUEST_URI", tmp->buf, tmp->len);
/* Set SCRIPT_URL
*/
if (! cherokee_buffer_is_empty (&conn->userdir)) {
cherokee_buffer_clean (tmp);
cherokee_buffer_add_str (tmp, "/~");
cherokee_buffer_add_buffer (tmp, &conn->userdir);
cherokee_buffer_add_buffer (tmp, &conn->request);
set_env (cgi, "SCRIPT_URL", tmp->buf, tmp->len);
} else {
set_env (cgi, "SCRIPT_URL", conn->request.buf, conn->request.len);
}
/* Set HTTPS and SERVER_PORT
*/
if (conn->socket.is_tls) {
set_env (cgi, "HTTPS", "on", 2);
} else {
set_env (cgi, "HTTPS", "off", 3);
}
set_env (cgi, "SERVER_PORT",
bind->server_port.buf,
bind->server_port.len);
/* Set SERVER_ADDR
*/
if (cherokee_buffer_is_empty (&bind->ip)) {
cherokee_sockaddr_t my_address;
cuint_t my_address_len = 0;
char ip_str[CHE_INET_ADDRSTRLEN+1];
my_address_len = sizeof(my_address);
re = getsockname (SOCKET_FD(&conn->socket),
(struct sockaddr *)&my_address,
&my_address_len);
if (re == 0) {
cherokee_ntop (my_address.sa_in.sin_family,
(struct sockaddr *) &my_address,
ip_str, sizeof(ip_str)-1);
set_env (cgi, "SERVER_ADDR",
ip_str, strlen(ip_str));
}
} else {
set_env (cgi, "SERVER_ADDR",
bind->server_address.buf,
bind->server_address.len);
}
/* Internal error redirection:
* It is okay if the QS is empty.
*/
if (! cherokee_buffer_is_empty (&conn->error_internal_url)) {
set_env (cgi, "REDIRECT_URL",
conn->error_internal_url.buf,
conn->error_internal_url.len);
set_env (cgi, "REDIRECT_QUERY_STRING",
conn->error_internal_qs.buf,
conn->error_internal_qs.len);
}
/* Authentication
*/
switch (conn->req_auth_type) {
case http_auth_nothing:
break;
case http_auth_basic:
set_env (cgi, "AUTH_TYPE", "Basic", 5);
break;
case http_auth_digest:
set_env (cgi, "AUTH_TYPE", "Digest", 6);
break;
}
/* HTTP variables
*/
ret = cherokee_header_get_known (&conn->header, header_accept, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_ACCEPT", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_accept_charset, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_ACCEPT_CHARSET", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_accept_encoding, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_ACCEPT_ENCODING", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_accept_language, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_ACCEPT_LANGUAGE", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_authorization, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_AUTHORIZATION", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_connection, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_CONNECTION", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_cookie, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_COOKIE", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_if_modified_since, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_IF_MODIFIED_SINCE", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_if_none_match, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_IF_NONE_MATCH", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_if_range, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_IF_RANGE", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_keepalive, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_KEEP_ALIVE", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_range, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_RANGE", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_referer, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_REFERER", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_user_agent, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_USER_AGENT", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_x_forwarded_for, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_X_FORWARDED_FOR", p, p_len);
}
ret = cherokee_header_get_known (&conn->header, header_x_forwarded_host, &p, &p_len);
if (ret == ret_ok) {
set_env (cgi, "HTTP_X_FORWARDED_HOST", p, p_len);
}
/* TODO: Fill the others CGI environment variables
*
* http://hoohoo.ncsa.uiuc.edu/cgi/env.html
* http://cgi-spec.golux.com/cgi-120-00a.html
*/
return ret_ok;
}
static ret_t
foreach_header_add_unknown_variable (cherokee_buffer_t *header,
cherokee_buffer_t *content,
void *data)
{
cuint_t i;
cherokee_handler_cgi_base_t *cgi = HDL_CGI_BASE(data);
/* Transfor 'Headers' to HTTP_HEADERS
* NOTE: header is a copy, it can be modified
*/
for (i=0; i<header->len; i++) {
if ((header->buf[i] >= 'a') &&
(header->buf[i] <= 'z'))
{
header->buf[i] -= ('a' - 'A');
} else if (header->buf[i] == '-') {
header->buf[i] = '_';
}
}
cherokee_buffer_prepend_str (header, "HTTP_");
/* Add it to the *CGI environment
*/
cgi->add_env_pair (cgi,
header->buf, header->len,
content->buf, content->len);
return ret_ok;
}
ret_t
cherokee_handler_cgi_base_build_envp (cherokee_handler_cgi_base_t *cgi, cherokee_connection_t *conn)
{
ret_t ret;
cherokee_list_t *i;
cherokee_buffer_t *name;
cuint_t len = 0;
const char *p = "";
cherokee_buffer_t tmp = CHEROKEE_BUF_INIT;
cherokee_handler_cgi_base_props_t *cgi_props = HANDLER_CGI_BASE_PROPS(cgi);
/* Add user defined variables at the beginning,
* these have precedence..
*/
list_for_each (i, &cgi_props->system_env) {
env_item_t *env = (env_item_t *)i;
cgi->add_env_pair (cgi,
env->env.buf, env->env.len,
env->val.buf, env->val.len);
}
/* Pass request headers.
* Usually X-Something..
*/
if (cgi_props->pass_req_headers) {
cherokee_header_foreach_unknown (&conn->header,
foreach_header_add_unknown_variable, cgi);
}
/* Add the basic enviroment variables
*/
ret = cherokee_handler_cgi_base_build_basic_env (cgi, cgi->add_env_pair, conn, &tmp);
if (unlikely (ret != ret_ok)) return ret;
/* SCRIPT_NAME:
*/
if (! cgi_props->check_file) {
/* SCGI or FastCGI:
*
* - If the SCGI is handling / it is ''
* - Otherwise, it is the web_directory.
*/
if (conn->web_directory.len > 1) {
cgi->add_env_pair (cgi, "SCRIPT_NAME", 11,
conn->web_directory.buf,
conn->web_directory.len);
} else {
cgi->add_env_pair (cgi, "SCRIPT_NAME", 11, "", 0);
}
} else {
/* CGI:
*
* It is the request without the pathinfo if it exists
*/
cherokee_buffer_clean (&tmp);
if (cherokee_buffer_is_empty (&cgi_props->script_alias)) {
/* cgi */
name = &cgi->executable;
if (conn->local_directory.len > 0){
p = name->buf + conn->local_directory.len;
len = (name->buf + name->len) - p;
} else {
p = name->buf;
len = name->len;
}
}
if (! cherokee_buffer_is_empty (&conn->userdir)) {
cherokee_buffer_add_str (&tmp, "/~");
cherokee_buffer_add_buffer (&tmp, &conn->userdir);
}
if (cherokee_connection_use_webdir(conn)) {
cherokee_buffer_add_buffer (&tmp, &conn->web_directory);
}
if (len > 0)
cherokee_buffer_add (&tmp, p, len);
cgi->add_env_pair (cgi, "SCRIPT_NAME", 11, tmp.buf, tmp.len);
}
/* Set PATH_TRANSLATED; only if PATH_INFO is set
*/
if (! cherokee_buffer_is_empty (&conn->pathinfo)) {
cherokee_buffer_add_buffer (&conn->local_directory,
&conn->pathinfo);
cgi->add_env_pair (cgi, "PATH_TRANSLATED", 15,
conn->local_directory.buf,
conn->local_directory.len);
cherokee_buffer_drop_ending (&conn->local_directory,
conn->pathinfo.len);
}
/* SCRIPT_FILENAME
* It depends on the type of CGI (CGI, SCGI o FastCGI):
* http://php.net/reserved.variables
*/
cherokee_buffer_mrproper (&tmp);
return ret_ok;
}
static ret_t
mix_headers (cherokee_buffer_t *target,
cherokee_buffer_t *source)
{
char tmp;
char *begin;
char *colon;
char *end, *end1, *end2;
begin = source->buf;
while ((begin != NULL) && *begin)
{
end1 = strchr (begin, CHR_CR);
end2 = strchr (begin, CHR_LF);
end = cherokee_min_str (end1, end2);
if (end == NULL) break;
end2 = end;
while ((*end2 == CHR_CR) || (*end2 == CHR_LF)) {
end2++;
}
/* Begin points to the beginning of a new line
*/
tmp = *end2;
*end2 = '\0';
colon = strstr (begin, ":");
*end2 = tmp;
if (colon != NULL) {
tmp = colon[1];
colon[1] = '\0';
end1 = strcasestr (target->buf, begin);
colon[1] = tmp;
if (end1 == NULL) {
cherokee_buffer_add (target, begin, end-begin);
cherokee_buffer_add_str (target, CRLF);
}
}
begin = end2;
}
return ret_ok;
}
ret_t
cherokee_handler_cgi_base_extract_path (cherokee_handler_cgi_base_t *cgi,
cherokee_boolean_t check_filename)
{
ret_t ret;
cint_t req_len;
cint_t local_len;
struct stat st;
cint_t pathinfo_len = 0;
cherokee_connection_t *conn = HANDLER_CONN(cgi);
cherokee_handler_cgi_base_props_t *props = HANDLER_CGI_BASE_PROPS(cgi);
/* ScriptAlias: If there is a ScriptAlias directive, it
* doesn't need to find the executable file..
*/
if (! cherokee_buffer_is_empty (&props->script_alias)) {
TRACE (ENTRIES, "Script alias '%s'\n", props->script_alias.buf);
if (cherokee_stat (props->script_alias.buf, &st) == -1) {
conn->error_code = http_not_found;
return ret_error;
}
cherokee_buffer_add_buffer (&cgi->executable, &props->script_alias);
/* Check the path_info even if it uses a scriptalias. The PATH_INFO
* is the rest of the substraction of request - configured directory.
*/
if (cherokee_connection_use_webdir (conn)) {
cherokee_buffer_add_buffer (&conn->pathinfo, &conn->request);
} else {
cherokee_buffer_add (&conn->pathinfo,
conn->request.buf + conn->web_directory.len,
conn->request.len - conn->web_directory.len);
}
return ret_ok;
}
/* No file checking: mainly for FastCGI and SCGI
*
* - If it's handling /, all the req. is path info
* - Otherwise, it is everything after conn->web_directory
*/
if (! props->check_file)
{
if (conn->web_directory.len == 1) {
cherokee_buffer_add_buffer (&conn->pathinfo, &conn->request);
} else {
cherokee_buffer_add (&conn->pathinfo,
conn->request.buf + conn->web_directory.len,
conn->request.len - conn->web_directory.len);
}
return ret_ok;
}
req_len = conn->request.len;
local_len = conn->local_directory.len;
cherokee_buffer_add_buffer (&conn->local_directory, &conn->request);
/* Build the pathinfo string
*/
if (pathinfo_len <= 0) {
cuint_t start = local_len - 1;
if (! check_filename) {
/* FastCGI and SCGI
*/
if (! cherokee_buffer_is_empty (&conn->web_directory)) {
start += conn->web_directory.len;
}
ret = cherokee_handler_cgi_base_split_pathinfo (cgi, &conn->local_directory, start, true);
if (ret != ret_ok) {
/* It couldn't find a pathinfo, so let's check if there is something
* we can do here: skip the next file and the rest is pathinfo.
*/
char *end = conn->local_directory.buf + conn->local_directory.len;
char *begin = conn->local_directory.buf + start + 1;
char *p = begin;
while ((p < end) && (*p != '/')) p++;
if (p < end) {
cherokee_buffer_add (&conn->pathinfo, p, end - p);
pathinfo_len = end - p;
cherokee_buffer_drop_ending (&conn->local_directory, pathinfo_len);
}
} else {
pathinfo_len = conn->pathinfo.len;
}
}
else {
/* CGI and phpCGI
*/
ret = cherokee_handler_cgi_base_split_pathinfo (cgi, &conn->local_directory, start, false);
if (unlikely(ret < ret_ok)) {
conn->error_code = http_not_found;
goto bye;
}
pathinfo_len = conn->pathinfo.len;
}
/* At this point:
* - conn->pathinfo: has been filled
* - conn->local_directory: doesn't contain the pathinfo
*/
}
TRACE (ENTRIES, "Pathinfo: '%s'\n", conn->pathinfo.buf);
cherokee_buffer_add_buffer (&cgi->executable, &conn->local_directory);
TRACE (ENTRIES, "Executable: '%s'\n", cgi->executable.buf);
/* Check if the file exists
*/
ret = ret_ok;
if (check_filename) {
if (cherokee_stat (conn->local_directory.buf, &st) == -1) {
conn->error_code = http_not_found;
ret = ret_error;
goto bye;
}
}
bye:
/* Clean up the mess
*/
cherokee_buffer_drop_ending (&conn->local_directory, (req_len - pathinfo_len));
return ret;
}
static ret_t
parse_header (cherokee_handler_cgi_base_t *cgi, cherokee_buffer_t *buffer)
{
ret_t ret;
char *end;
char *end1;
char *end2;
char *begin;
cherokee_connection_t *conn = HANDLER_CONN(cgi);
if (cherokee_buffer_is_empty (buffer) || buffer->len <= 5)
return ret_ok;
/* It is possible that the header ends with CRLF_CRLF
* In this case, we have to remove the last two characters
*/
if ((buffer->len > 4) &&
(strncmp (CRLF_CRLF, buffer->buf + buffer->len - 4, 4) == 0)) {
cherokee_buffer_drop_ending (buffer, 2);
}
TRACE (ENTRIES, "CGI header: '%s'\n", buffer->buf);
/* Process the header line by line
*/
begin = buffer->buf;
while ((begin != NULL) && *begin)
{
end1 = strchr (begin, CHR_CR);
end2 = strchr (begin, CHR_LF);
end = cherokee_min_str (end1, end2);
if (end == NULL) break;
end2 = end;
while ((*end2 == CHR_CR) || (*end2 == CHR_LF))
end2++;
if (strncasecmp ("Status: ", begin, 8) == 0) {
int code;
char status[4];
memcpy (status, begin+8, 3);
status[3] = '\0';
ret = cherokee_atoi (status, &code);
if ((ret != ret_ok) || (code < 100)) {
conn->error_code = http_internal_error;
return ret_error;
}
cherokee_buffer_remove_chunk (buffer, begin - buffer->buf, end2 - begin);
end2 = begin;
conn->error_code = code;
continue;
}
else if (strncasecmp ("HTTP/", begin, 5) == 0) {
int code;
char status[4];
memcpy (status, begin+9, 3);
status[3] = '\0';
ret = cherokee_atoi (status, &code);
if ((ret != ret_ok) || (code < 100)) {
conn->error_code = http_internal_error;
return ret_error;
}
cherokee_buffer_remove_chunk (buffer, begin - buffer->buf, end2 - begin);
end2 = begin;
conn->error_code = code;
continue;
}
else if (strncasecmp ("Content-Length: ", begin, 16) == 0) {
if (cherokee_connection_should_include_length(conn)) {
char saved = *end;
*end = '\0';
cgi->content_length = strtoll (begin+16, (char **)NULL, 10);
*end = saved;
HANDLER(cgi)->support |= hsupport_length;
}
cherokee_buffer_remove_chunk (buffer, begin - buffer->buf, end2 - begin);
end2 = begin;
}
else if (strncasecmp ("Location: ", begin, 10) == 0) {
cherokee_buffer_add (&conn->redirect, begin+10, end - (begin+10));
cherokee_buffer_remove_chunk (buffer, begin - buffer->buf, end2 - begin);
end2 = begin;
}
else if (strncasecmp ("Content-Encoding: ", begin, 18) == 0) {
BIT_SET (conn->options, conn_op_cant_encoder);
}
else if ((HANDLER_CGI_BASE_PROPS(cgi)->allow_xsendfile) &&
(strncasecmp ("X-Sendfile: ", begin, 12) == 0))
{
cherokee_buffer_add (&cgi->xsendfile, begin+12, end - (begin+12));
cherokee_buffer_remove_chunk (buffer, begin - buffer->buf, end2 - begin);
end2 = begin;
TRACE (ENTRIES, "Found X-Sendfile header: '%s'\n", cgi->xsendfile.buf);
}
else if ((HANDLER_CGI_BASE_PROPS(cgi)->allow_xsendfile) &&
(strncasecmp ("X-Accel-Redirect: ", begin, 18) == 0))
{
cherokee_buffer_add (&cgi->xsendfile, begin+18, end - (begin+18));
cherokee_buffer_remove_chunk (buffer, begin - buffer->buf, end2 - begin);
end2 = begin;
TRACE (ENTRIES, "Found X-Accel-Redirect header: '%s'\n", cgi->xsendfile.buf);
}
begin = end2;
}
return ret_ok;
}
ret_t
cherokee_handler_cgi_base_add_headers (cherokee_handler_cgi_base_t *cgi,
cherokee_buffer_t *outbuf)
{
ret_t ret;
int len;
char *content;
cuint_t end_len;
cherokee_buffer_t *inbuf = &cgi->data;
cherokee_connection_t *conn = HANDLER_CONN(cgi);
/* Read some information
*/
ret = cgi->read_from_cgi (cgi, inbuf);
switch (ret) {
case ret_ok:
case ret_eof_have_data:
break;
case ret_eof:
case ret_error:
case ret_eagain:
return ret;
default:
RET_UNKNOWN(ret);
return ret_error;
}
/* Look the end of headers
*/
ret = cherokee_find_header_end (inbuf, &content, &end_len);
switch (ret) {
case ret_ok:
break;
case ret_error:
return ret_error;
default:
return (cgi->got_eof) ? ret_eof : ret_eagain;
}
/* Copy the header
*/
len = content - inbuf->buf;
cherokee_buffer_ensure_size (outbuf, len+6);
cherokee_buffer_add (outbuf, inbuf->buf, len);
cherokee_buffer_add_str (outbuf, CRLF_CRLF);
/* Drop out the headers, we already have a copy
*/
cherokee_buffer_move_to_begin (inbuf, len + end_len);
/* From this moment, it can handle errors
*/
if (HANDLER_CGI_BASE_PROPS(cgi)->is_error_handler) {
HANDLER(cgi)->support |= hsupport_error;
}
/* Parse the header.. it is likely we will have something to do with it.
*/
ret = parse_header (cgi, outbuf);
if (unlikely (ret != ret_ok)) {
return ret;
}
/* Handle X-Sendfile
*/
if (! cherokee_buffer_is_empty (&cgi->xsendfile))
{
cherokee_buffer_t cgi_header = CHEROKEE_BUF_INIT;
/* Instance the 'file' sub-handler
*/
handler_file_props.use_cache = true;
ret = cherokee_handler_file_new ((cherokee_handler_t **) &cgi->file_handler,
conn, MODULE_PROPS(&handler_file_props));
if (ret != ret_ok)
return ret_error;
ret = cherokee_handler_file_custom_init (cgi->file_handler, &cgi->xsendfile);
if (ret != ret_ok)
return ret_error;
/* Work out the header
*/
cherokee_buffer_add_buffer (&cgi_header, outbuf);
cherokee_buffer_clean (outbuf);
ret = cherokee_handler_file_add_headers (cgi->file_handler, outbuf);
if (ret != ret_ok) {
cherokee_buffer_mrproper (&cgi_header);
return ret_error;
}
/* Overwrite the handler properties
*/
HANDLER(cgi)->support = HANDLER(cgi->file_handler)->support;
conn->chunked_encoding = false;
mix_headers (outbuf, &cgi_header);
cherokee_buffer_mrproper (&cgi_header);
return ret_ok;
}
/* Content-Length response header
*/
if (HANDLER_SUPPORTS (cgi, hsupport_length))
{
cherokee_buffer_add_str (outbuf, "Content-Length: ");
cherokee_buffer_add_ullong10 (outbuf, (cullong_t) cgi->content_length);
cherokee_buffer_add_str (outbuf, CRLF);
}
/* Redirection without custom status
*/
if ((conn->error_code == http_ok) &&
(! cherokee_buffer_is_empty (&conn->redirect)))
{
TRACE(ENTRIES, "Redirection without custom status. Setting %d\n", 302);
conn->error_code = http_moved_temporarily;
}
return ret_ok;
}
ret_t
cherokee_handler_cgi_base_step (cherokee_handler_cgi_base_t *cgi,
cherokee_buffer_t *outbuf)
{
ret_t ret;
cherokee_buffer_t *inbuf = &cgi->data;
/* Is it replying a "X-Sendfile" request?
*/
if (cgi->file_handler) {
return cherokee_handler_file_step (cgi->file_handler, outbuf);
}
/* If there is some data waiting to be sent in the CGI buffer, move it
* to the connection buffer and continue..
*/
if (! cherokee_buffer_is_empty (&cgi->data)) {
TRACE (ENTRIES, "sending stored data: %d bytes\n", cgi->data.len);
cherokee_buffer_add_buffer (outbuf, &cgi->data);
cherokee_buffer_clean (&cgi->data);
if (cgi->got_eof) {
return ret_eof_have_data;
}
return ret_ok;
}
/* Read some information from the CGI
*/
ret = cgi->read_from_cgi (cgi, inbuf);
if (inbuf->len > 0) {
cherokee_buffer_add_buffer (outbuf, inbuf);
cherokee_buffer_clean (inbuf);
}
return ret;
}
ret_t
cherokee_handler_cgi_base_split_pathinfo (cherokee_handler_cgi_base_t *cgi,
cherokee_buffer_t *buf,
int init_pos,
int allow_dirs)
{
ret_t ret;
char *pathinfo;
int pathinfo_len;
cherokee_connection_t *conn = HANDLER_CONN(cgi);
/* Look for the pathinfo
*/
ret = cherokee_split_pathinfo (buf, init_pos, allow_dirs, &pathinfo, &pathinfo_len);
if (ret == ret_not_found)
return ret;
/* Split the string
*/
if (pathinfo_len > 0) {
cherokee_buffer_add (&conn->pathinfo, pathinfo, pathinfo_len);
cherokee_buffer_drop_ending (buf, pathinfo_len);
}
TRACE (ENTRIES, "Pathinfo '%s'\n", conn->pathinfo.buf);
return ret_ok;
}
Jump to Line
Something went wrong with that request. Please try again.