/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2005 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see .
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
/*
* http.c - HTTP protocol server and client implementation
*
* Implements major parts of the Hypertext Transfer Protocol HTTP/1.1 (RFC 2616)
* See http://www.w3.org/Protocols/rfc2616/rfc2616.txt
*
* Lars Wirzenius
*/
/* XXX re-implement socket pools, with idle connection killing to
save sockets */
/* XXX implement http_abort */
/* XXX give maximum input size */
/* XXX kill http_get_real */
/* XXX the proxy exceptions list should be a dict, I guess */
/* XXX set maximum number of concurrent connections to same host, total? */
/* XXX 100 status codes. */
/* XXX stop destroying persistent connections when a request is redirected */
#include
#include
#include
#include
#include
#include
#include
#include "gwlib.h"
#include "gwlib/regex.h"
/* comment this out if you don't want HTTP responses to be dumped */
#define DUMP_RESPONSE 1
/* define http client connections timeout in seconds (set to -1 for disable) */
#define HTTP_CLIENT_TIMEOUT 240
/* define http server connections timeout in seconds (set to -1 for disable) */
#define HTTP_SERVER_TIMEOUT 60
/***********************************************************************
* Stuff used in several sub-modules.
*/
/*
* Default port to connect to for HTTP connections.
*/
enum { HTTP_PORT = 80,
HTTPS_PORT = 443 };
/*
* Status of this module.
*/
static enum {
limbo,
running,
terminating
} run_status = limbo;
/*
* Which interface to use for outgoing HTTP requests.
*/
static Octstr *http_interface = NULL;
/*
* Read some headers, i.e., until the first empty line (read and discard
* the empty line as well). Return -1 for error, 0 for all headers read,
* 1 for more headers to follow.
*/
static int read_some_headers(Connection *conn, List *headers)
{
Octstr *line, *prev;
if (gwlist_len(headers) == 0)
prev = NULL;
else
prev = gwlist_get(headers, gwlist_len(headers) - 1);
for (;;) {
line = conn_read_line(conn);
if (line == NULL) {
if (conn_eof(conn) || conn_error(conn))
return -1;
return 1;
}
if (octstr_len(line) == 0) {
octstr_destroy(line);
break;
}
if (isspace(octstr_get_char(line, 0)) && prev != NULL) {
octstr_append(prev, line);
octstr_destroy(line);
} else {
gwlist_append(headers, line);
prev = line;
}
}
return 0;
}
/*
* Check that the HTTP version string is valid. Return -1 for invalid,
* 0 for version 1.0, 1 for 1.x.
*/
static int parse_http_version(Octstr *version)
{
Octstr *prefix;
long prefix_len;
int digit;
prefix = octstr_imm("HTTP/1.");
prefix_len = octstr_len(prefix);
if (octstr_ncompare(version, prefix, prefix_len) != 0)
return -1;
if (octstr_len(version) != prefix_len + 1)
return -1;
digit = octstr_get_char(version, prefix_len);
if (!isdigit(digit))
return -1;
if (digit == '0')
return 0;
return 1;
}
/***********************************************************************
* Proxy support.
*/
/*
* Data and functions needed to support proxy operations. If proxy_hostname
* is NULL, no proxy is used.
*/
static Mutex *proxy_mutex = NULL;
static Octstr *proxy_hostname = NULL;
static int proxy_port = 0;
static Octstr *proxy_username = NULL;
static Octstr *proxy_password = NULL;
static List *proxy_exceptions = NULL;
static regex_t *proxy_exceptions_regex = NULL;
static void proxy_add_authentication(List *headers)
{
Octstr *os;
if (proxy_username == NULL || proxy_password == NULL)
return;
os = octstr_format("%S:%S", proxy_username, proxy_password);
octstr_binary_to_base64(os);
octstr_strip_blanks(os);
octstr_insert(os, octstr_imm("Basic "), 0);
http_header_add(headers, "Proxy-Authorization", octstr_get_cstr(os));
octstr_destroy(os);
}
static void proxy_init(void)
{
proxy_mutex = mutex_create();
proxy_exceptions = gwlist_create();
}
static void proxy_shutdown(void)
{
http_close_proxy();
mutex_destroy(proxy_mutex);
proxy_mutex = NULL;
}
static int proxy_used_for_host(Octstr *host, Octstr *url)
{
int i;
mutex_lock(proxy_mutex);
if (proxy_hostname == NULL) {
mutex_unlock(proxy_mutex);
return 0;
}
for (i = 0; i < gwlist_len(proxy_exceptions); ++i) {
if (octstr_compare(host, gwlist_get(proxy_exceptions, i)) == 0) {
mutex_unlock(proxy_mutex);
return 0;
}
}
if (proxy_exceptions_regex != NULL && gw_regex_match_pre(proxy_exceptions_regex, url)) {
mutex_unlock(proxy_mutex);
return 0;
}
mutex_unlock(proxy_mutex);
return 1;
}
void http_use_proxy(Octstr *hostname, int port, List *exceptions,
Octstr *username, Octstr *password, Octstr *exceptions_regex)
{
Octstr *e;
int i;
gw_assert(run_status == running);
gw_assert(hostname != NULL);
gw_assert(octstr_len(hostname) > 0);
gw_assert(port > 0);
http_close_proxy();
mutex_lock(proxy_mutex);
proxy_hostname = octstr_duplicate(hostname);
proxy_port = port;
proxy_exceptions = gwlist_create();
for (i = 0; i < gwlist_len(exceptions); ++i) {
e = gwlist_get(exceptions, i);
debug("gwlib.http", 0, "HTTP: Proxy exception `%s'.",
octstr_get_cstr(e));
gwlist_append(proxy_exceptions, octstr_duplicate(e));
}
if (exceptions_regex != NULL &&
(proxy_exceptions_regex = gw_regex_comp(exceptions_regex, REG_EXTENDED)) == NULL)
panic(0, "Could not compile pattern '%s'", octstr_get_cstr(exceptions_regex));
proxy_username = octstr_duplicate(username);
proxy_password = octstr_duplicate(password);
debug("gwlib.http", 0, "Using proxy <%s:%d>",
octstr_get_cstr(proxy_hostname), proxy_port);
mutex_unlock(proxy_mutex);
}
void http_close_proxy(void)
{
gw_assert(run_status == running || run_status == terminating);
mutex_lock(proxy_mutex);
proxy_port = 0;
octstr_destroy(proxy_hostname);
octstr_destroy(proxy_username);
octstr_destroy(proxy_password);
proxy_hostname = NULL;
proxy_username = NULL;
proxy_password = NULL;
gwlist_destroy(proxy_exceptions, octstr_destroy_item);
gw_regex_destroy(proxy_exceptions_regex);
proxy_exceptions = NULL;
proxy_exceptions_regex = NULL;
mutex_unlock(proxy_mutex);
}
/***********************************************************************
* Common functions for reading request or result entities.
*/
/*
* Value to pass to entity_create.
*/
enum body_expectation {
/*
* Message must not have a body, even if the headers indicate one.
* (i.e. response to HEAD method).
*/
expect_no_body,
/*
* Message will have a body if Content-Length or Transfer-Encoding
* headers are present (i.e. most request methods).
*/
expect_body_if_indicated,
/*
* Message will have a body, possibly zero-length.
* (i.e. 200 OK responses to a GET method.)
*/
expect_body
};
enum entity_state {
reading_headers,
reading_chunked_body_len,
reading_chunked_body_data,
reading_chunked_body_crlf,
reading_chunked_body_trailer,
reading_body_until_eof,
reading_body_with_length,
body_error,
entity_done
};
typedef struct {
List *headers;
Octstr *body;
enum body_expectation expect_state;
enum entity_state state;
long chunked_body_chunk_len;
long expected_body_len;
} HTTPEntity;
/*
* The rules for message bodies (length and presence) are defined
* in RFC2616 paragraph 4.3 and 4.4.
*/
static void deduce_body_state(HTTPEntity *ent)
{
Octstr *h = NULL;
if (ent->expect_state == expect_no_body) {
ent->state = entity_done;
return;
}
ent->state = body_error; /* safety net */
h = http_header_find_first(ent->headers, "Transfer-Encoding");
if (h != NULL) {
octstr_strip_blanks(h);
if (octstr_str_compare(h, "chunked") != 0) {
error(0, "HTTP: Unknown Transfer-Encoding <%s>",
octstr_get_cstr(h));
ent->state = body_error;
} else {
ent->state = reading_chunked_body_len;
}
octstr_destroy(h);
return;
}
h = http_header_find_first(ent->headers, "Content-Length");
if (h != NULL) {
if (octstr_parse_long(&ent->expected_body_len, h, 0, 10) == -1 ||
ent->expected_body_len < 0) {
error(0, "HTTP: Content-Length header wrong: <%s>",
octstr_get_cstr(h));
ent->state = body_error;
} else if (ent->expected_body_len == 0) {
ent->state = entity_done;
} else {
ent->state = reading_body_with_length;
}
octstr_destroy(h);
return;
}
if (ent->expect_state == expect_body)
ent->state = reading_body_until_eof;
else
ent->state = entity_done;
}
/*
* Create a HTTPEntity structure suitable for reading the expected
* result or request message and decoding the transferred entity (if any).
* See the definition of enum body_expectation for the possible values
* of exp.
*/
static HTTPEntity *entity_create(enum body_expectation exp)
{
HTTPEntity *ent;
ent = gw_malloc(sizeof(*ent));
ent->headers = http_create_empty_headers();
ent->body = octstr_create("");
ent->chunked_body_chunk_len = -1;
ent->expected_body_len = -1;
ent->state = reading_headers;
ent->expect_state = exp;
return ent;
}
static void entity_destroy(HTTPEntity *ent)
{
if (ent == NULL)
return;
http_destroy_headers(ent->headers);
octstr_destroy(ent->body);
gw_free(ent);
}
static void read_chunked_body_len(HTTPEntity *ent, Connection *conn)
{
Octstr *os;
long len;
os = conn_read_line(conn);
if (os == NULL) {
if (conn_error(conn) || conn_eof(conn))
ent->state = body_error;
return;
}
if (octstr_parse_long(&len, os, 0, 16) == -1) {
octstr_destroy(os);
ent->state = body_error;
return;
}
octstr_destroy(os);
if (len == 0)
ent->state = reading_chunked_body_trailer;
else {
ent->state = reading_chunked_body_data;
ent->chunked_body_chunk_len = len;
}
}
static void read_chunked_body_data(HTTPEntity *ent, Connection *conn)
{
Octstr *os;
os = conn_read_fixed(conn, ent->chunked_body_chunk_len);
if (os == NULL) {
if (conn_error(conn) || conn_eof(conn))
ent->state = body_error;
} else {
octstr_append(ent->body, os);
octstr_destroy(os);
ent->state = reading_chunked_body_crlf;
}
}
static void read_chunked_body_crlf(HTTPEntity *ent, Connection *conn)
{
Octstr *os;
os = conn_read_line(conn);
if (os == NULL) {
if (conn_error(conn) || conn_eof(conn))
ent->state = body_error;
} else {
octstr_destroy(os);
ent->state = reading_chunked_body_len;
}
}
static void read_chunked_body_trailer(HTTPEntity *ent, Connection *conn)
{
int ret;
ret = read_some_headers(conn, ent->headers);
if (ret == -1)
ent->state = body_error;
if (ret == 0)
ent->state = entity_done;
}
static void read_body_until_eof(HTTPEntity *ent, Connection *conn)
{
Octstr *os;
while ((os = conn_read_everything(conn)) != NULL) {
octstr_append(ent->body, os);
octstr_destroy(os);
}
if (conn_error(conn))
ent->state = body_error;
if (conn_eof(conn))
ent->state = entity_done;
}
static void read_body_with_length(HTTPEntity *ent, Connection *conn)
{
Octstr *os;
os = conn_read_fixed(conn, ent->expected_body_len);
if (os == NULL) {
if (conn_error(conn) || conn_eof(conn))
ent->state = body_error;
return;
}
octstr_destroy(ent->body);
ent->body = os;
ent->state = entity_done;
}
/*
* Read headers and body (if any) from this connection. Return 0 if it's
* complete, 1 if we expect more input, and -1 if there is something wrong.
*/
static int entity_read(HTTPEntity *ent, Connection *conn)
{
int ret;
enum entity_state old_state;
/*
* In this loop, each state will process as much input as it needs
* and then switch to the next state, unless it's a final state in
* which case it returns directly, or unless it needs more input.
* So keep looping as long as the state changes.
*/
do {
old_state = ent->state;
switch (ent->state) {
case reading_headers:
ret = read_some_headers(conn, ent->headers);
if (ret == 0)
deduce_body_state(ent);
if (ret < 0)
return -1;
break;
case reading_chunked_body_len:
read_chunked_body_len(ent, conn);
break;
case reading_chunked_body_data:
read_chunked_body_data(ent, conn);
break;
case reading_chunked_body_crlf:
read_chunked_body_crlf(ent, conn);
break;
case reading_chunked_body_trailer:
read_chunked_body_trailer(ent, conn);
break;
case reading_body_until_eof:
read_body_until_eof(ent, conn);
break;
case reading_body_with_length:
read_body_with_length(ent, conn);
break;
case body_error:
return -1;
case entity_done:
return 0;
default:
panic(0, "Internal error: Invalid HTTPEntity state.");
}
} while (ent->state != old_state);
/*
* If we got here, then the loop ended because a non-final state
* needed more input.
*/
return 1;
}
/***********************************************************************
* HTTP client interface.
*/
/*
* Internal lists of completely unhandled requests and requests for which
* a request has been sent but response has not yet been read.
*/
static List *pending_requests = NULL;
/*
* Have background threads been started?
*/
static Mutex *client_thread_lock = NULL;
static volatile sig_atomic_t client_threads_are_running = 0;
/*
* Set of all connections to all servers. Used with conn_register to
* do I/O on several connections with a single thread.
*/
static FDSet *client_fdset = NULL;
/*
* Maximum number of HTTP redirections to follow. Making this infinite
* could cause infinite looping if the redirections loop.
*/
#define HTTP_MAX_FOLLOW 5
/*
* The implemented HTTP method strings
* Order is sequenced by the enum in the header
*/
static char *http_methods[] = {
"GET", "POST", "HEAD"
};
/*
* Information about a server we've connected to.
*/
typedef struct {
HTTPCaller *caller;
void *request_id;
int method; /* uses enums from http.h for the HTTP methods */
Octstr *url; /* the full URL, including scheme, host, etc. */
Octstr *uri; /* the HTTP URI path only */
List *request_headers;
Octstr *request_body; /* NULL for GET or HEAD, non-NULL for POST */
enum {
connecting,
request_not_sent,
reading_status,
reading_entity,
transaction_done
} state;
long status;
int persistent;
HTTPEntity *response; /* Can only be NULL if status < 0 */
Connection *conn;
Octstr *host;
long port;
int follow_remaining;
Octstr *certkeyfile;
int ssl;
Octstr *username; /* For basic authentication */
Octstr *password;
} HTTPServer;
static int send_request(HTTPServer *trans);
static Octstr *build_response(List *headers, Octstr *body);
static HTTPServer *server_create(HTTPCaller *caller, int method, Octstr *url,
List *headers, Octstr *body, int follow_remaining,
Octstr *certkeyfile)
{
HTTPServer *trans;
trans = gw_malloc(sizeof(*trans));
trans->caller = caller;
trans->request_id = NULL;
trans->method = method;
trans->url = octstr_duplicate(url);
trans->uri = NULL;
trans->request_headers = http_header_duplicate(headers);
trans->request_body = octstr_duplicate(body);
trans->state = request_not_sent;
trans->status = -1;
trans->persistent = 0;
trans->response = NULL;
trans->conn = NULL;
trans->host = NULL;
trans->port = 0;
trans->username = NULL;
trans->password = NULL;
trans->follow_remaining = follow_remaining;
trans->certkeyfile = octstr_duplicate(certkeyfile);
trans->ssl = 0;
return trans;
}
static void server_destroy(void *p)
{
HTTPServer *trans;
trans = p;
octstr_destroy(trans->url);
octstr_destroy(trans->uri);
http_destroy_headers(trans->request_headers);
trans->request_headers = NULL;
octstr_destroy(trans->request_body);
entity_destroy(trans->response);
octstr_destroy(trans->host);
octstr_destroy(trans->certkeyfile);
octstr_destroy(trans->username);
octstr_destroy(trans->password);
gw_free(trans);
}
/*
* Pool of open, but unused connections to servers or proxies. Key is
* "servername:port", value is List with Connection objects.
*/
static Dict *conn_pool;
static Mutex *conn_pool_lock;
static void conn_pool_item_destroy(void *item)
{
gwlist_destroy(item, (void(*)(void*))conn_destroy);
}
static void conn_pool_init(void)
{
conn_pool = dict_create(1024, conn_pool_item_destroy);
conn_pool_lock = mutex_create();
}
static void conn_pool_shutdown(void)
{
dict_destroy(conn_pool);
mutex_destroy(conn_pool_lock);
}
static inline Octstr *conn_pool_key(Octstr *host, int port)
{
return octstr_format("%S:%d", host, port);
}
static Connection *conn_pool_get(Octstr *host, int port, int ssl, Octstr *certkeyfile,
Octstr *our_host)
{
Octstr *key;
List *list = NULL;
Connection *conn = NULL;
int retry;
do {
retry = 0;
key = conn_pool_key(host, port);
mutex_lock(conn_pool_lock);
list = dict_get(conn_pool, key);
if (list != NULL)
conn = gwlist_extract_first(list);
mutex_unlock(conn_pool_lock);
/*
* Note: we don't hold conn_pool_lock when we check/destroy/unregister
* connection because otherwise we can deadlock! And it's even better
* not to delay other threads while we check connection.
*/
if (conn != NULL) {
#ifdef USE_KEEPALIVE
/* unregister our server disconnect callback */
conn_unregister(conn);
#endif
/*
* Check whether the server has closed the connection while
* it has been in the pool.
*/
conn_wait(conn, 0);
if (conn_eof(conn) || conn_error(conn)) {
debug("gwlib.http", 0, "HTTP:conn_pool_get: Server closed connection, destroying it <%s><%p>.",
octstr_get_cstr(key), conn, conn_get_id(conn));
conn_destroy(conn);
retry = 1;
conn = NULL;
}
}
octstr_destroy(key);
} while(retry == 1);
if (conn == NULL) {
#ifdef HAVE_LIBSSL
if (ssl)
conn = conn_open_ssl(host, port, certkeyfile, our_host);
else
#endif /* HAVE_LIBSSL */
conn = conn_open_tcp_nb(host, port, our_host);
debug("gwlib.http", 0, "HTTP: Opening connection to `%s:%d' (fd=%d).",
octstr_get_cstr(host), port, conn_get_id(conn));
} else {
debug("gwlib.http", 0, "HTTP: Reusing connection to `%s:%d' (fd=%d).",
octstr_get_cstr(host), port, conn_get_id(conn));
}
return conn;
}
#ifdef USE_KEEPALIVE
static void check_pool_conn(Connection *conn, void *data)
{
Octstr *key = data;
if (run_status != running) {
conn_unregister(conn);
return;
}
/* check if connection still ok */
conn_wait(conn, 0);
if (conn_error(conn) || conn_eof(conn)) {
List *list;
mutex_lock(conn_pool_lock);
list = dict_get(conn_pool, key);
if (list != NULL && gwlist_delete_equal(list, conn) > 0) {
/*
* ok, connection was still within pool. So it's
* safe to destroy this connection.
*/
debug("gwlib.http", 0, "HTTP: Server closed connection, destroying it <%s><%p>.",
octstr_get_cstr(key), conn, conn_get_id(conn));
conn_unregister(conn);
conn_destroy(conn);
}
/*
* it's perfectly valid if connection was not found in connection pool because
* in 'conn_pool_get' we first removed connection from pool with conn_pool_lock locked
* and then check connection for errors with conn_pool_lock unlocked. In the meantime
* fdset's poller may call us. So just ignore such "dummy" call.
*/
mutex_unlock(conn_pool_lock);
}
}
static void conn_pool_put(Connection *conn, Octstr *host, int port)
{
Octstr *key;
List *list;
key = conn_pool_key(host, port);
mutex_lock(conn_pool_lock);
list = dict_get(conn_pool, key);
if (list == NULL) {
list = gwlist_create();
dict_put(conn_pool, key, list);
}
gwlist_append(list, conn);
/* register connection to get server disconnect */
conn_register_real(conn, client_fdset, check_pool_conn, key, octstr_destroy_item);
mutex_unlock(conn_pool_lock);
}
#endif
HTTPCaller *http_caller_create(void)
{
HTTPCaller *caller;
caller = gwlist_create();
gwlist_add_producer(caller);
return caller;
}
void http_caller_destroy(HTTPCaller *caller)
{
gwlist_destroy(caller, server_destroy);
}
void http_caller_signal_shutdown(HTTPCaller *caller)
{
gwlist_remove_producer(caller);
}
static Octstr *get_redirection_location(HTTPServer *trans)
{
if (trans->status < 0 || trans->follow_remaining <= 0)
return NULL;
/* check for the redirection response codes */
if (trans->status != HTTP_MOVED_PERMANENTLY &&
trans->status != HTTP_FOUND && trans->status != HTTP_SEE_OTHER &&
trans->status != HTTP_TEMPORARY_REDIRECT)
return NULL;
if (trans->response == NULL)
return NULL;
return http_header_find_first(trans->response->headers, "Location");
}
/*
* Read and parse the status response line from an HTTP server.
* Fill in trans->persistent and trans->status with the findings.
* Return -1 for error, 1 for status line not yet available, 0 for OK.
*/
static int client_read_status(HTTPServer *trans)
{
Octstr *line, *version;
long space;
int ret;
line = conn_read_line(trans->conn);
if (line == NULL) {
if (conn_eof(trans->conn) || conn_error(trans->conn))
return -1;
return 1;
}
debug("gwlib.http", 0, "HTTP: Status line: <%s>", octstr_get_cstr(line));
space = octstr_search_char(line, ' ', 0);
if (space == -1)
goto error;
version = octstr_copy(line, 0, space);
ret = parse_http_version(version);
octstr_destroy(version);
if (ret == -1)
goto error;
trans->persistent = ret;
octstr_delete(line, 0, space + 1);
space = octstr_search_char(line, ' ', 0);
if (space == -1)
goto error;
octstr_truncate(line, space);
if (octstr_parse_long(&trans->status, line, 0, 10) == -1)
goto error;
octstr_destroy(line);
return 0;
error:
error(0, "HTTP: Malformed status line from HTTP server: <%s>",
octstr_get_cstr(line));
octstr_destroy(line);
return -1;
}
static int response_expectation(int method, int status)
{
if (status == HTTP_NO_CONTENT ||
status == HTTP_NOT_MODIFIED ||
http_status_class(status) == HTTP_STATUS_PROVISIONAL ||
method == HTTP_METHOD_HEAD)
return expect_no_body;
else
return expect_body;
}
static void handle_transaction(Connection *conn, void *data)
{
HTTPServer *trans;
int ret;
Octstr *h;
int rc;
trans = data;
if (run_status != running) {
conn_unregister(conn);
return;
}
while (trans->state != transaction_done) {
switch (trans->state) {
case connecting:
debug("gwlib.http", 0, "Get info about connecting socket");
if (conn_get_connect_result(trans->conn) != 0) {
debug("gwlib.http", 0, "Socket not connected");
goto error;
}
if ((rc = send_request(trans)) == 0) {
trans->state = reading_status;
} else {
debug("gwlib.http", 0, "Failed while sending request");
goto error;
}
break;
case reading_status:
ret = client_read_status(trans);
if (ret < 0) {
/*
* Couldn't read the status from the socket. This may mean
* that the socket had been closed by the server after an
* idle timeout.
*/
debug("gwlib.http",0,"Failed while reading status");
goto error;
} else if (ret == 0) {
/* Got the status, go read headers and body next. */
trans->state = reading_entity;
trans->response =
entity_create(response_expectation(trans->method, trans->status));
} else
return;
break;
case reading_entity:
ret = entity_read(trans->response, conn);
if (ret < 0) {
debug("gwlib.http",0,"Failed reading entity");
goto error;
} else if (ret == 0 &&
http_status_class(trans->status) == HTTP_STATUS_PROVISIONAL) {
/* This was a provisional reply; get the real one now. */
trans->state = reading_status;
entity_destroy(trans->response);
trans->response = NULL;
} else if (ret == 0) {
trans->state = transaction_done;
#ifdef DUMP_RESPONSE
/* Dump the response */
debug("gwlib.http", 0, "HTTP: Received response:");
h = build_response(trans->response->headers, trans->response->body);
octstr_dump(h, 0);
octstr_destroy(h);
#endif
} else {
return;
}
break;
default:
panic(0, "Internal error: Invalid HTTPServer state.");
}
}
conn_unregister(trans->conn);
h = http_header_find_first(trans->response->headers, "Connection");
if (h != NULL && octstr_compare(h, octstr_imm("close")) == 0)
trans->persistent = 0;
octstr_destroy(h);
#ifdef USE_KEEPALIVE
if (trans->persistent) {
if (proxy_used_for_host(trans->host, trans->url))
conn_pool_put(trans->conn, proxy_hostname, proxy_port);
else
conn_pool_put(trans->conn, trans->host, trans->port);
} else
#endif
conn_destroy(trans->conn);
trans->conn = NULL;
/*
* Check if the HTTP server told us to look somewhere else,
* hence if we got one of the following response codes:
* HTTP_MOVED_PERMANENTLY (301)
* HTTP_FOUND (302)
* HTTP_SEE_OTHER (303)
* HTTP_TEMPORARY_REDIRECT (307)
*/
if ((h = get_redirection_location(trans)) != NULL) {
/*
* This is a redirected response, we have to follow.
* Clean up all trans stuff for the next request we do.
*/
octstr_strip_blanks(h);
octstr_destroy(trans->url);
octstr_destroy(trans->host);
trans->port = 0;
octstr_destroy(trans->uri);
octstr_destroy(trans->username);
octstr_destroy(trans->password);
trans->host = NULL;
trans->port = 0;
trans->uri = NULL;
trans->username = NULL;
trans->password = NULL;
trans->ssl = 0;
trans->url = h; /* apply new absolute URL to next request */
trans->state = request_not_sent;
trans->status = -1;
http_destroy_headers(trans->response->headers);
trans->response->headers = gwlist_create();
octstr_destroy(trans->response->body);
trans->response->body = octstr_create("");
--trans->follow_remaining;
conn_destroy(trans->conn);
trans->conn = NULL;
/* re-inject request to queue */
gwlist_produce(pending_requests, trans);
} else {
/* handle this response as usual */
gwlist_produce(trans->caller, trans);
}
return;
error:
conn_unregister(trans->conn);
conn_destroy(trans->conn);
trans->conn = NULL;
error(0, "Couldn't fetch <%s>", octstr_get_cstr(trans->url));
trans->status = -1;
gwlist_produce(trans->caller, trans);
}
/*
* Build a complete HTTP request given the host, port, path and headers.
* Add Host: and Content-Length: headers (and others that may be necessary).
* Return the request as an Octstr.
*/
static Octstr *build_request(char *method_name, Octstr *path_or_url,
Octstr *host, long port, List *headers,
Octstr *request_body)
{
/* XXX headers missing */
Octstr *request;
int i;
request = octstr_format("%s %S HTTP/1.1\r\n",
method_name, path_or_url);
octstr_format_append(request, "Host: %S", host);
if (port != HTTP_PORT)
octstr_format_append(request, ":%ld", port);
octstr_append(request, octstr_imm("\r\n"));
for (i = 0; headers != NULL && i < gwlist_len(headers); ++i) {
octstr_append(request, gwlist_get(headers, i));
octstr_append(request, octstr_imm("\r\n"));
}
octstr_append(request, octstr_imm("\r\n"));
if (request_body != NULL)
octstr_append(request, request_body);
return request;
}
/*
* Re-build the HTTP response given the headers and the body.
* Return the response as an Octstr.
*/
static Octstr *build_response(List *headers, Octstr *body)
{
Octstr *response;
int i;
response = octstr_create("");
for (i = 0; headers != NULL && i < gwlist_len(headers); ++i) {
octstr_append(response, gwlist_get(headers, i));
octstr_append(response, octstr_imm("\r\n"));
}
octstr_append(response, octstr_imm("\r\n"));
if (body != NULL)
octstr_append(response, body);
return response;
}
HTTPURLParse *http_urlparse_create(void)
{
HTTPURLParse *p;
p = gw_malloc(sizeof(HTTPURLParse));
p->url = NULL;
p->scheme = NULL;
p->host = NULL;
p->port = 0;
p->user = NULL;
p->pass = NULL;
p->path = NULL;
p->query = NULL;
p->fragment = NULL;
return p;
}
void http_urlparse_destroy(HTTPURLParse *p)
{
gw_assert(p != NULL);
octstr_destroy(p->url);
octstr_destroy(p->scheme);
octstr_destroy(p->host);
octstr_destroy(p->user);
octstr_destroy(p->pass);
octstr_destroy(p->path);
octstr_destroy(p->query);
octstr_destroy(p->fragment);
gw_free(p);
}
void parse_dump(HTTPURLParse *p)
{
if (p == NULL)
return;
debug("http.parse_url",0,"Parsing URL `%s':", octstr_get_cstr(p->url));
debug("http.parse_url",0," Scheme: %s", octstr_get_cstr(p->scheme));
debug("http.parse_url",0," Host: %s", octstr_get_cstr(p->host));
debug("http.parse_url",0," Port: %ld", p->port);
debug("http.parse_url",0," Username: %s", octstr_get_cstr(p->user));
debug("http.parse_url",0," Password: %s", octstr_get_cstr(p->pass));
debug("http.parse_url",0," Path: %s", octstr_get_cstr(p->path));
debug("http.parse_url",0," Query: %s", octstr_get_cstr(p->query));
debug("http.parse_url",0," Fragment: %s", octstr_get_cstr(p->fragment));
}
/*
* Parse the URL to get all components, which are: scheme, hostname,
* port, username, password, path (URI), query (the CGI parameter list),
* fragment (#).
*
* On success return the HTTPURLParse structure, otherwise NULL if the URL
* seems malformed.
*
* We assume HTTP URLs of the form specified in "3.2.2 http URL" in
* RFC 2616:
*
* http_URL = "http:" "//" [ userid : password "@"] host [ ":" port ] [ abs_path [ "?" query ]]
*/
HTTPURLParse *parse_url(Octstr *url)
{
HTTPURLParse *p;
Octstr *prefix, *prefix_https;
long prefix_len;
int host_len, colon, slash, at, auth_sep, query;
host_len = colon = slash = at = auth_sep = query = 0;
prefix = octstr_imm("http://");
prefix_https = octstr_imm("https://");
prefix_len = octstr_len(prefix);
if (octstr_case_search(url, prefix, 0) != 0) {
if (octstr_case_search(url, prefix_https, 0) == 0) {
#ifdef HAVE_LIBSSL
debug("gwlib.http", 0, "HTTPS URL; Using SSL for the connection");
prefix = prefix_https;
prefix_len = octstr_len(prefix_https);
#else
error(0, "Attempt to use HTTPS <%s> but SSL not compiled in",
octstr_get_cstr(url));
return NULL;
#endif
} else {
error(0, "URL <%s> doesn't start with `%s' nor `%s'",
octstr_get_cstr(url), octstr_get_cstr(prefix),
octstr_get_cstr(prefix_https));
return NULL;
}
}
/* an URL should be more (at least one charset) then the scheme itself */
if (octstr_len(url) == prefix_len) {
error(0, "URL <%s> is malformed.", octstr_get_cstr(url));
return NULL;
}
/* check if colon and slashes are within scheme */
colon = octstr_search_char(url, ':', prefix_len);
slash = octstr_search_char(url, '/', prefix_len);
if (colon == prefix_len || slash == prefix_len) {
error(0, "URL <%s> is malformed.", octstr_get_cstr(url));
return NULL;
}
/* create struct and add values succesively while parsing */
p = http_urlparse_create();
p->url = octstr_duplicate(url);
p->scheme = octstr_duplicate(prefix);
/* try to parse authentication separator */
at = octstr_search_char(url, '@', prefix_len);
if (at != -1) {
if ((slash == -1 || ( slash != -1 && at < slash))) {
auth_sep = octstr_search_char(url, ':', prefix_len);
if (auth_sep != -1 && (auth_sep < at)) {
octstr_set_char(url, auth_sep, '@');
colon = octstr_search_char(url, ':', prefix_len);
}
} else {
at = -1;
}
}
/*
* We have to watch out here for 4 cases:
* a) hostname, no port or path
* b) hostname, port, no path
* c) hostname, path, no port
* d) hostname, port and path
*/
/* we only have the hostname, no port or path. */
if (slash == -1 && colon == -1) {
host_len = octstr_len(url) - prefix_len;
#ifdef HAVE_LIBSSL
p->port = (octstr_compare(p->scheme, octstr_imm("https://")) == 0) ?
HTTPS_PORT : HTTP_PORT;
#else
p->port = HTTP_PORT;
#endif /* HAVE_LIBSSL */
}
/* we have a port, but no path. */
else if (slash == -1) {
host_len = colon - prefix_len;
if (octstr_parse_long((long*) &(p->port), url, colon + 1, 10) == -1) {
error(0, "URL <%s> has malformed port number.",
octstr_get_cstr(url));
http_urlparse_destroy(p);
return NULL;
}
}
/* we have a path, but no port. */
else if (colon == -1 || colon > slash) {
host_len = slash - prefix_len;
#ifdef HAVE_LIBSSL
p->port = (octstr_compare(p->scheme, octstr_imm("https://")) == 0) ?
HTTPS_PORT : HTTP_PORT;
#else
p->port = HTTP_PORT;
#endif /* HAVE_LIBSSL */
}
/* we have both, path and port. */
else if (colon < slash) {
host_len = colon - prefix_len;
if (octstr_parse_long((long*) &(p->port), url, colon + 1, 10) == -1) {
error(0, "URL <%s> has malformed port number.",
octstr_get_cstr(url));
http_urlparse_destroy(p);
return NULL;
}
/* none of the above, so there is something wrong here */
} else {
error(0, "Internal error in URL parsing logic.");
http_urlparse_destroy(p);
return NULL;
}
/* there was an authenticator separator, so try to parse
* the username and password credentials */
if (at != -1) {
int at2;
at2 = octstr_search_char(url, '@', prefix_len);
p->user = octstr_copy(url, prefix_len, at2 - prefix_len);
p->pass = (at2 != at) ? octstr_copy(url, at2 + 1, at - at2 - 1) : NULL;
if (auth_sep != -1)
octstr_set_char(url, auth_sep, ':');
host_len = host_len - at + prefix_len - 1;
prefix_len = at + 1;
}
/* query (CGI vars) */
query = octstr_search_char(url, '?', (slash == -1) ? prefix_len : slash);
if (query != -1) {
p->query = octstr_copy(url, query + 1, octstr_len(url));
}
/* path */
p->path = (slash == -1) ?
octstr_create("/") : ((query != -1) && (query > slash) ?
octstr_copy(url, slash, query - slash) :
octstr_copy(url, slash, octstr_len(url) - slash));
/* hostname */
p->host = octstr_copy(url, prefix_len,
(query == -1 || slash != -1) ? host_len : query - prefix_len);
/* XXX add fragment too */
/* dump components */
parse_dump(p);
return p;
}
/* copy all relevant parsed data to the server info struct */
static void parse2trans(HTTPURLParse *p, HTTPServer *t)
{
if (p == NULL || t == NULL)
return;
if (p->user && !t->username)
t->username = octstr_duplicate(p->user);
if (p->pass && !t->password)
t->password = octstr_duplicate(p->pass);
if (p->host && !t->host)
t->host = octstr_duplicate(p->host);
if (p->port && !t->port)
t->port = p->port;
if (p->path && !t->uri) {
t->uri = octstr_duplicate(p->path);
if (p->query) { /* add the query too */
octstr_append_char(t->uri, '?');
octstr_append(t->uri, p->query);
}
}
t->ssl = (p->scheme && (octstr_compare(p->scheme, octstr_imm("https://")) == 0)
&& !t->ssl) ? 1 : 0;
}
static Connection *get_connection(HTTPServer *trans)
{
Connection *conn = NULL;
Octstr *host;
HTTPURLParse *p;
int port;
/* if the parsing has not yet been done, then do it now */
if (!trans->host && trans->port == 0 && trans->url != NULL) {
if ((p = parse_url(trans->url)) != NULL) {
parse2trans(p, trans);
http_urlparse_destroy(p);
} else {
goto error;
}
}
if (proxy_used_for_host(trans->host, trans->url)) {
host = proxy_hostname;
port = proxy_port;
} else {
host = trans->host;
port = trans->port;
}
conn = conn_pool_get(host, port, trans->ssl, trans->certkeyfile,
http_interface);
if (conn == NULL)
goto error;
return conn;
error:
conn_destroy(conn);
error(0, "Couldn't send request to <%s>", octstr_get_cstr(trans->url));
return NULL;
}
/*
* Build and send the HTTP request. Return 0 for success or -1 for error.
*/
static int send_request(HTTPServer *trans)
{
char buf[128];
Octstr *request = NULL;
if (trans->method == HTTP_METHOD_POST) {
/*
* Add a Content-Length header. Override an existing one, if
* necessary. We must have an accurate one in order to use the
* connection for more than a single request.
*/
http_header_remove_all(trans->request_headers, "Content-Length");
snprintf(buf, sizeof(buf), "%ld", octstr_len(trans->request_body));
http_header_add(trans->request_headers, "Content-Length", buf);
}
/*
* ok, this has to be an GET or HEAD request method then,
* if it contains a body, then this is not HTTP conform, so at
* least warn the user
*/
else if (trans->request_body != NULL) {
warning(0, "HTTP: GET or HEAD method request contains body:");
octstr_dump(trans->request_body, 0);
}
/*
* we have to assume all values in trans are already set
* by parse_url() before calling this.
*/
if (trans->username != NULL)
http_add_basic_auth(trans->request_headers, trans->username,
trans->password);
if (proxy_used_for_host(trans->host, trans->url)) {
proxy_add_authentication(trans->request_headers);
request = build_request(http_method2name(trans->method),
trans->url, trans->host, trans->port,
trans->request_headers,
trans->request_body);
} else {
request = build_request(http_method2name(trans->method), trans->uri,
trans->host, trans->port,
trans->request_headers,
trans->request_body);
}
debug("gwlib.http", 0, "HTTP: Sending request:");
octstr_dump(request, 0);
if (conn_write(trans->conn, request) == -1)
goto error;
octstr_destroy(request);
return 0;
error:
conn_destroy(trans->conn);
trans->conn = NULL;
octstr_destroy(request);
error(0, "Couldn't send request to <%s>", octstr_get_cstr(trans->url));
return -1;
}
/*
* This thread starts the transaction: it connects to the server and sends
* the request. It then sends the transaction to the read_response_thread
* via started_requests_queue.
*/
static void write_request_thread(void *arg)
{
HTTPServer *trans;
int rc;
while (run_status == running) {
trans = gwlist_consume(pending_requests);
if (trans == NULL)
break;
gw_assert(trans->state == request_not_sent);
/*
* get the connection to use
* also calls parse_url() to populate the trans values
*/
trans->conn = get_connection(trans);
if (trans->conn == NULL)
gwlist_produce(trans->caller, trans);
else if (conn_is_connected(trans->conn) == 0) {
debug("gwlib.http", 0, "Socket connected at once");
if ((rc = send_request(trans)) == 0) {
trans->state = reading_status;
conn_register(trans->conn, client_fdset, handle_transaction,
trans);
} else {
gwlist_produce(trans->caller, trans);
}
} else { /* Socket not connected, wait for connection */
debug("gwlib.http", 0, "Socket connecting");
trans->state = connecting;
conn_register(trans->conn, client_fdset, handle_transaction, trans);
}
}
}
static void start_client_threads(void)
{
if (!client_threads_are_running) {
/*
* To be really certain, we must repeat the test, but use the
* lock first. If the test failed, however, we _know_ we've
* already initialized. This strategy of double testing avoids
* using the lock more than a few times at startup.
*/
mutex_lock(client_thread_lock);
if (!client_threads_are_running) {
client_fdset = fdset_create_real(HTTP_CLIENT_TIMEOUT);
if (gwthread_create(write_request_thread, NULL) == -1) {
error(0, "HTTP: Could not start client write_request thread.");
fdset_destroy(client_fdset);
client_threads_are_running = 0;
} else
client_threads_are_running = 1;
}
mutex_unlock(client_thread_lock);
}
}
void http_set_interface(const Octstr *our_host)
{
http_interface = octstr_duplicate(our_host);
}
void http_start_request(HTTPCaller *caller, int method, Octstr *url, List *headers,
Octstr *body, int follow, void *id, Octstr *certkeyfile)
{
HTTPServer *trans;
int follow_remaining;
if (follow)
follow_remaining = HTTP_MAX_FOLLOW;
else
follow_remaining = 0;
trans = server_create(caller, method, url, headers, body, follow_remaining,
certkeyfile);
if (id == NULL)
/* We don't leave this NULL so http_receive_result can use NULL
* to signal no more requests */
trans->request_id = http_start_request;
else
trans->request_id = id;
gwlist_produce(pending_requests, trans);
start_client_threads();
}
void *http_receive_result_real(HTTPCaller *caller, int *status, Octstr **final_url,
List **headers, Octstr **body, int blocking)
{
HTTPServer *trans;
void *request_id;
if (blocking == 0)
trans = gwlist_extract_first(caller);
else
trans = gwlist_consume(caller);
if (trans == NULL)
return NULL;
request_id = trans->request_id;
*status = trans->status;
if (trans->status >= 0) {
*final_url = trans->url;
*headers = trans->response->headers;
*body = trans->response->body;
trans->url = NULL;
trans->response->headers = NULL;
trans->response->body = NULL;
} else {
*final_url = NULL;
*headers = NULL;
*body = NULL;
}
server_destroy(trans);
return request_id;
}
int http_get_real(int method, Octstr *url, List *request_headers, Octstr **final_url,
List **reply_headers, Octstr **reply_body)
{
HTTPCaller *caller;
int status;
void *ret;
caller = http_caller_create();
http_start_request(caller, method, url, request_headers,
NULL, 1, http_get_real, NULL);
ret = http_receive_result(caller, &status, final_url,
reply_headers, reply_body);
http_caller_destroy(caller);
if (ret == NULL)
return -1;
return status;
}
static void client_init(void)
{
pending_requests = gwlist_create();
gwlist_add_producer(pending_requests);
client_thread_lock = mutex_create();
}
static void client_shutdown(void)
{
gwlist_remove_producer(pending_requests);
gwthread_join_every(write_request_thread);
client_threads_are_running = 0;
gwlist_destroy(pending_requests, server_destroy);
mutex_destroy(client_thread_lock);
fdset_destroy(client_fdset);
client_fdset = NULL;
octstr_destroy(http_interface);
http_interface = NULL;
}
/***********************************************************************
* HTTP server interface.
*/
/*
* Information about a client that has connected to the server we implement.
*/
struct HTTPClient {
int port;
Connection *conn;
Octstr *ip;
enum {
reading_request_line,
reading_request,
request_is_being_handled,
sending_reply
} state;
int method; /* HTTP_METHOD_ value */
Octstr *url;
int use_version_1_0;
int persistent_conn;
unsigned long conn_time; /* store time for timeouting */
HTTPEntity *request;
};
/* List with all active HTTPClient's */
static List *active_connections;
static HTTPClient *client_create(int port, Connection *conn, Octstr *ip)
{
HTTPClient *p;
#ifdef HAVE_LIBSSL
if (conn_get_ssl(conn))
debug("gwlib.http", 0, "HTTP: Creating SSL-enabled HTTPClient for `%s', using cipher '%s'.",
octstr_get_cstr(ip), SSL_get_cipher_version(conn_get_ssl(conn)));
else
#endif
debug("gwlib.http", 0, "HTTP: Creating HTTPClient for `%s'.", octstr_get_cstr(ip));
p = gw_malloc(sizeof(*p));
p->port = port;
p->conn = conn;
p->ip = ip;
p->state = reading_request_line;
p->url = NULL;
p->use_version_1_0 = 0;
p->persistent_conn = 1;
p->conn_time = time(NULL);
p->request = NULL;
debug("gwlib.http", 0, "HTTP: Created HTTPClient area %p.", p);
/* add this client to active_connections */
gwlist_produce(active_connections, p);
return p;
}
static void client_destroy(void *client)
{
HTTPClient *p;
if (client == NULL)
return;
p = client;
/* drop this client from active_connections list */
gwlist_lock(active_connections);
if (gwlist_delete_equal(active_connections, p) != 1)
panic(0, "HTTP: Race condition in client_destroy(%p) detected!", client);
gwlist_unlock(active_connections);
debug("gwlib.http", 0, "HTTP: Destroying HTTPClient area %p.", p);
gw_assert_allocated(p, __FILE__, __LINE__, __func__);
debug("gwlib.http", 0, "HTTP: Destroying HTTPClient for `%s'.",
octstr_get_cstr(p->ip));
conn_destroy(p->conn);
octstr_destroy(p->ip);
octstr_destroy(p->url);
entity_destroy(p->request);
gw_free(p);
}
static void client_reset(HTTPClient *p)
{
debug("gwlib.http", 0, "HTTP: Resetting HTTPClient for `%s'.",
octstr_get_cstr(p->ip));
p->state = reading_request_line;
p->conn_time = time(NULL);
gw_assert(p->request == NULL);
}
/*
* Checks whether the client connection is meant to be persistent or not.
* Returns 1 for true, 0 for false.
*/
static int client_is_persistent(List *headers, int use_version_1_0)
{
Octstr *h = http_header_find_first(headers, "Connection");
if (h == NULL) {
return !use_version_1_0;
} else {
if (!use_version_1_0) {
if (octstr_case_compare(h, octstr_imm("keep-alive")) == 0) {
octstr_destroy(h);
return 1;
} else {
octstr_destroy(h);
return 0;
}
} else if (octstr_case_compare(h, octstr_imm("close")) == 0) {
octstr_destroy(h);
return 0;
}
octstr_destroy(h);
}
return 1;
}
/*
* Port specific lists of clients with requests.
*/
struct port {
List *clients_with_requests;
Counter *active_consumers;
};
static Mutex *port_mutex = NULL;
static Dict *port_collection = NULL;
static int port_match(void *client, void *port)
{
return ((HTTPClient*)client)->port == *((int*)port);
}
static void port_init(void)
{
port_mutex = mutex_create();
port_collection = dict_create(1024, NULL);
/* create list with all active_connections */
active_connections = gwlist_create();
}
static void port_shutdown(void)
{
mutex_destroy(port_mutex);
dict_destroy(port_collection);
/* destroy active_connections list */
gwlist_destroy(active_connections, client_destroy);
}
static Octstr *port_key(int port)
{
return octstr_format("%d", port);
}
static void port_add(int port)
{
Octstr *key;
struct port *p;
key = port_key(port);
mutex_lock(port_mutex);
if ((p = dict_get(port_collection, key)) == NULL) {
p = gw_malloc(sizeof(*p));
p->clients_with_requests = gwlist_create();
gwlist_add_producer(p->clients_with_requests);
p->active_consumers = counter_create();
dict_put(port_collection, key, p);
} else {
warning(0, "HTTP: port_add called for existing port (%d)", port);
}
mutex_unlock(port_mutex);
octstr_destroy(key);
}
static void port_remove(int port)
{
Octstr *key;
struct port *p;
List *l;
HTTPClient *client;
key = port_key(port);
mutex_lock(port_mutex);
p = dict_remove(port_collection, key);
mutex_unlock(port_mutex);
octstr_destroy(key);
if (p == NULL) {
error(0, "HTTP: Could not find port (%d) in port_collection.", port);
return;
}
gwlist_remove_producer(p->clients_with_requests);
while (counter_value(p->active_consumers) > 0)
gwthread_sleep(0.1); /* Reasonable use of busy waiting. */
gwlist_destroy(p->clients_with_requests, client_destroy);
counter_destroy(p->active_consumers);
gw_free(p);
/*
* In order to avoid race conditions with FDSet thread, we
* destroy Clients for this port in two steps:
* 1) unregister from fdset with gwlist_lock held, so client_destroy
* cannot destroy our client that we currently use
* 2) without gwlist_lock held destroy every client, we can do this
* because we only one thread that can use this client struct
*/
gwlist_lock(active_connections);
l = gwlist_search_all(active_connections, &port, port_match);
while(l != NULL && (client = gwlist_extract_first(l)) != NULL)
conn_unregister(client->conn);
gwlist_unlock(active_connections);
gwlist_destroy(l, NULL);
while((client = gwlist_search(active_connections, &port, port_match)) != NULL)
client_destroy(client);
}
static void port_put_request(HTTPClient *client)
{
Octstr *key;
struct port *p;
mutex_lock(port_mutex);
key = port_key(client->port);
p = dict_get(port_collection, key);
octstr_destroy(key);
gw_assert(p != NULL);
gwlist_produce(p->clients_with_requests, client);
mutex_unlock(port_mutex);
}
static HTTPClient *port_get_request(int port)
{
Octstr *key;
struct port *p;
HTTPClient *client;
mutex_lock(port_mutex);
key = port_key(port);
p = dict_get(port_collection, key);
octstr_destroy(key);
if (p == NULL) {
client = NULL;
mutex_unlock(port_mutex);
} else {
counter_increase(p->active_consumers);
mutex_unlock(port_mutex); /* Placement of this unlock is tricky. */
client = gwlist_consume(p->clients_with_requests);
counter_decrease(p->active_consumers);
}
return client;
}
/*
* Maximum number of servers (ports) we have open at the same time.
*/
#define MAX_SERVERS 32
/*
* Variables related to server side implementation.
*/
static Mutex *server_thread_lock = NULL;
static volatile sig_atomic_t server_thread_is_running = 0;
static long server_thread_id = -1;
static FDSet *server_fdset = NULL;
static List *new_server_sockets = NULL;
static List *closed_server_sockets = NULL;
static int keep_servers_open = 0;
static int parse_request_line(int *method, Octstr **url,
int *use_version_1_0, Octstr *line)
{
List *words;
Octstr *version;
Octstr *method_str;
int ret;
words = octstr_split_words(line);
if (gwlist_len(words) != 3) {
gwlist_destroy(words, octstr_destroy_item);
return -1;
}
method_str = gwlist_get(words, 0);
*url = gwlist_get(words, 1);
version = gwlist_get(words, 2);
gwlist_destroy(words, NULL);
if (octstr_compare(method_str, octstr_imm("GET")) == 0)
*method = HTTP_METHOD_GET;
else if (octstr_compare(method_str, octstr_imm("POST")) == 0)
*method = HTTP_METHOD_POST;
else if (octstr_compare(method_str, octstr_imm("HEAD")) == 0)
*method = HTTP_METHOD_HEAD;
else
goto error;
ret = parse_http_version(version);
if (ret < 0)
goto error;
*use_version_1_0 = !ret;
octstr_destroy(method_str);
octstr_destroy(version);
return 0;
error:
octstr_destroy(method_str);
octstr_destroy(*url);
octstr_destroy(version);
*url = NULL;
return -1;
}
static void receive_request(Connection *conn, void *data)
{
HTTPClient *client;
Octstr *line;
int ret;
if (run_status != running) {
conn_unregister(conn);
return;
}
client = data;
for (;;) {
switch (client->state) {
case reading_request_line:
line = conn_read_line(conn);
if (line == NULL) {
if (conn_eof(conn) || conn_error(conn))
goto error;
return;
}
ret = parse_request_line(&client->method, &client->url,
&client->use_version_1_0, line);
octstr_destroy(line);
/* client sent bad request? */
if (ret == -1) {
/*
* mark client as not persistent in order to destroy connection
* afterwards
*/
client->persistent_conn = 0;
/* unregister connection, http_send_reply handle this */
conn_unregister(conn);
http_send_reply(client, HTTP_BAD_REQUEST, NULL, NULL);
return;
}
/*
* RFC2616 (4.3) says we should read a message body if there
* is one, even on GET requests.
*/
client->request = entity_create(expect_body_if_indicated);
client->state = reading_request;
break;
case reading_request:
ret = entity_read(client->request, conn);
if (ret < 0)
goto error;
if (ret == 0) {
client->state = request_is_being_handled;
conn_unregister(conn);
port_put_request(client);
}
return;
case sending_reply:
/* Implicit conn_unregister() and _destroy */
if (conn_error(conn))
goto error;
if (conn_outbuf_len(conn) > 0)
return;
/* Reply has been sent completely */
if (!client->persistent_conn) {
/*
* in order to avoid race conditions while conn will be destroyed but
* conn is still in use, we call conn_unregister explicit here because
* conn_unregister call uses locks
*/
conn_unregister(conn);
client_destroy(client);
return;
}
/* Start reading another request */
client_reset(client);
break;
default:
panic(0, "Internal error: HTTPClient state is wrong.");
}
}
error:
/*
* in order to avoid race conditions while conn will be destroyed but
* conn is still in use, we call conn_unregister explicit here because
* conn_unregister call uses locks
*/
conn_unregister(conn);
client_destroy(client);
}
struct server {
int fd;
int port;
int ssl;
};
static void server_thread(void *dummy)
{
struct pollfd tab[MAX_SERVERS];
int ports[MAX_SERVERS];
int ssl[MAX_SERVERS];
long i, j, n, fd;
int *portno;
struct server *p;
struct sockaddr_in addr;
socklen_t addrlen;
Connection *conn;
HTTPClient *client;
int ret;
n = 0;
while (run_status == running && keep_servers_open) {
if (n == 0 || (n < MAX_SERVERS && gwlist_len(new_server_sockets) > 0)) {
p = gwlist_consume(new_server_sockets);
if (p == NULL) {
debug("gwlib.http", 0, "HTTP: No new servers. Quitting.");
break;
}
tab[n].fd = p->fd;
tab[n].events = POLLIN;
ports[n] = p->port;
ssl[n] = p->ssl;
++n;
gw_free(p);
}
if ((ret = gwthread_poll(tab, n, -1.0)) == -1) {
if (errno != EINTR) /* a signal was caught during poll() function */
warning(errno, "HTTP: gwthread_poll failed.");
continue;
}
for (i = 0; i < n; ++i) {
if (tab[i].revents & POLLIN) {
addrlen = sizeof(addr);
fd = accept(tab[i].fd, (struct sockaddr *) &addr, &addrlen);
if (fd == -1) {
error(errno, "HTTP: Error accepting a client.");
} else {
Octstr *client_ip = host_ip(addr);
/*
* Be aware that conn_wrap_fd() will return NULL if SSL
* handshake has failed, so we only client_create() if
* there is an conn.
*/
if ((conn = conn_wrap_fd(fd, ssl[i]))) {
client = client_create(ports[i], conn, client_ip);
conn_register(conn, server_fdset, receive_request, client);
} else {
error(0, "HTTP: unsuccessful SSL handshake for client `%s'",
octstr_get_cstr(client_ip));
octstr_destroy(client_ip);
}
}
}
}
while ((portno = gwlist_extract_first(closed_server_sockets)) != NULL) {
for (i = 0; i < n; ++i) {
if (ports[i] == *portno) {
(void) close(tab[i].fd);
port_remove(ports[i]);
tab[i].fd = -1;
ports[i] = -1;
ssl[i] = 0;
}
}
gw_free(portno);
}
j = 0;
for (i = 0; i < n; ++i) {
if (tab[i].fd != -1) {
tab[j] = tab[i];
ports[j] = ports[i];
ssl[j] = ssl[i];
++j;
}
}
n = j;
}
/* make sure we close all ports */
for (i = 0; i < n; ++i) {
(void) close(tab[i].fd);
port_remove(ports[i]);
}
server_thread_id = -1;
}
static void start_server_thread(void)
{
if (!server_thread_is_running) {
/*
* To be really certain, we must repeat the test, but use the
* lock first. If the test failed, however, we _know_ we've
* already initialized. This strategy of double testing avoids
* using the lock more than a few times at startup.
*/
mutex_lock(server_thread_lock);
if (!server_thread_is_running) {
server_fdset = fdset_create_real(HTTP_SERVER_TIMEOUT);
server_thread_id = gwthread_create(server_thread, NULL);
server_thread_is_running = 1;
}
mutex_unlock(server_thread_lock);
}
}
int http_open_port_if(int port, int ssl, Octstr *interface)
{
struct server *p;
if (ssl)
info(0, "HTTP: Opening SSL server at port %d.", port);
else
info(0, "HTTP: Opening server at port %d.", port);
p = gw_malloc(sizeof(*p));
p->port = port;
p->ssl = ssl;
p->fd = make_server_socket(port, (interface ? octstr_get_cstr(interface) : NULL));
if (p->fd == -1) {
gw_free(p);
return -1;
}
port_add(port);
gwlist_produce(new_server_sockets, p);
keep_servers_open = 1;
start_server_thread();
gwthread_wakeup(server_thread_id);
return 0;
}
int http_open_port(int port, int ssl)
{
return http_open_port_if(port, ssl, NULL);
}
void http_close_port(int port)
{
int *p;
p = gw_malloc(sizeof(*p));
*p = port;
gwlist_produce(closed_server_sockets, p);
gwthread_wakeup(server_thread_id);
}
void http_close_all_ports(void)
{
if (server_thread_id != -1) {
keep_servers_open = 0;
gwthread_wakeup(server_thread_id);
gwthread_join_every(server_thread);
server_thread_is_running = 0;
fdset_destroy(server_fdset);
server_fdset = NULL;
}
}
/*
* Parse CGI variables from the path given in a GET. Return a list
* of HTTPCGIvar pointers. Modify the url so that the variables are
* removed.
*/
static List *parse_cgivars(Octstr *url)
{
HTTPCGIVar *v;
List *list;
int query, et, equals;
Octstr *arg, *args;
query = octstr_search_char(url, '?', 0);
if (query == -1)
return gwlist_create();
args = octstr_copy(url, query + 1, octstr_len(url));
octstr_truncate(url, query);
list = gwlist_create();
while (octstr_len(args) > 0) {
et = octstr_search_char(args, '&', 0);
if (et == -1)
et = octstr_len(args);
arg = octstr_copy(args, 0, et);
octstr_delete(args, 0, et + 1);
equals = octstr_search_char(arg, '=', 0);
if (equals == -1)
equals = octstr_len(arg);
v = gw_malloc(sizeof(HTTPCGIVar));
v->name = octstr_copy(arg, 0, equals);
v->value = octstr_copy(arg, equals + 1, octstr_len(arg));
octstr_url_decode(v->name);
octstr_url_decode(v->value);
octstr_destroy(arg);
gwlist_append(list, v);
}
octstr_destroy(args);
return list;
}
HTTPClient *http_accept_request(int port, Octstr **client_ip, Octstr **url,
List **headers, Octstr **body,
List **cgivars)
{
HTTPClient *client;
client = port_get_request(port);
if (client == NULL) {
debug("gwlib.http", 0, "HTTP: No clients with requests, quitting.");
return NULL;
}
*client_ip = octstr_duplicate(client->ip);
*url = client->url;
*headers = client->request->headers;
*body = client->request->body;
*cgivars = parse_cgivars(client->url);
if (client->method != HTTP_METHOD_POST) {
octstr_destroy(*body);
*body = NULL;
}
client->persistent_conn = client_is_persistent(client->request->headers,
client->use_version_1_0);
client->url = NULL;
client->request->headers = NULL;
client->request->body = NULL;
entity_destroy(client->request);
client->request = NULL;
return client;
}
/*
* The http_send_reply(...) uses this function to determinate the
* reason pahrase for a status code.
*/
static const char *http_reason_phrase(int status)
{
switch (status) {
case HTTP_OK:
return "OK"; /* 200 */
case HTTP_CREATED:
return "Created"; /* 201 */
case HTTP_ACCEPTED:
return "Accepted"; /* 202 */
case HTTP_NO_CONTENT:
return "No Content"; /* 204 */
case HTTP_RESET_CONTENT:
return "Reset Content"; /* 205 */
case HTTP_MOVED_PERMANENTLY:
return "Moved Permanently"; /* 301 */
case HTTP_FOUND:
return "Found"; /* 302 */
case HTTP_SEE_OTHER:
return "See Other"; /* 303 */
case HTTP_NOT_MODIFIED:
return "Not Modified"; /* 304 */
case HTTP_TEMPORARY_REDIRECT:
return "Temporary Redirect"; /* 307 */
case HTTP_BAD_REQUEST:
return "Bad Request"; /* 400 */
case HTTP_UNAUTHORIZED:
return "Unauthorized"; /* 401 */
case HTTP_FORBIDDEN:
return "Forbidden"; /* 403 */
case HTTP_NOT_FOUND:
return "Not Found"; /* 404 */
case HTTP_BAD_METHOD:
return "Method Not Allowed"; /* 405 */
case HTTP_NOT_ACCEPTABLE:
return "Not Acceptable"; /* 406 */
case HTTP_REQUEST_ENTITY_TOO_LARGE:
return "Request Entity Too Large"; /* 413 */
case HTTP_UNSUPPORTED_MEDIA_TYPE:
return "Unsupported Media Type"; /* 415 */
case HTTP_INTERNAL_SERVER_ERROR:
return "Internal Server Error"; /* 500 */
case HTTP_NOT_IMPLEMENTED:
return "Not Implemented"; /* 501 */
case HTTP_BAD_GATEWAY:
return "Bad Gateway"; /* 502 */
}
return "Foo";
}
void http_send_reply(HTTPClient *client, int status, List *headers,
Octstr *body)
{
Octstr *response;
long i;
int ret;
if (client->use_version_1_0)
response = octstr_format("HTTP/1.0 %d %s\r\n", status, http_reason_phrase(status));
else
response = octstr_format("HTTP/1.1 %d %s\r\n", status, http_reason_phrase(status));
/* identify ourselfs */
octstr_format_append(response, "Server: " GW_NAME "/%s\r\n", GW_VERSION);
octstr_format_append(response, "Content-Length: %ld\r\n",
octstr_len(body));
/*
* RFC2616, sec. 8.1.2.1 says that if the server chooses to close the
* connection, it *should* send a coresponding header
*/
if (!client->use_version_1_0 && !client->persistent_conn)
octstr_format_append(response, "Connection: close\r\n");
for (i = 0; i < gwlist_len(headers); ++i)
octstr_format_append(response, "%S\r\n", gwlist_get(headers, i));
octstr_format_append(response, "\r\n");
if (body != NULL && client->method != HTTP_METHOD_HEAD)
octstr_append(response, body);
ret = conn_write(client->conn, response);
octstr_destroy(response);
/* obey return code of conn_write() */
/* sending response was successful */
if (ret == 0) {
/* HTTP/1.0 or 1.1, hence keep-alive or keep-alive */
if (!client->persistent_conn) {
client_destroy(client);
} else {
/* XXX mark this HTTPClient in the keep-alive cleaner thread */
client_reset(client);
conn_register(client->conn, server_fdset, receive_request, client);
}
}
/* queued for sending, we don't want to block */
else if (ret == 1) {
client->state = sending_reply;
conn_register(client->conn, server_fdset, receive_request, client);
}
/* error while sending response */
else {
client_destroy(client);
}
}
void http_close_client(HTTPClient *client)
{
client_destroy(client);
}
static void server_init(void)
{
new_server_sockets = gwlist_create();
gwlist_add_producer(new_server_sockets);
closed_server_sockets = gwlist_create();
server_thread_lock = mutex_create();
}
static void destroy_struct_server(void *p)
{
struct server *pp;
pp = p;
(void) close(pp->fd);
gw_free(pp);
}
static void destroy_int_pointer(void *p)
{
(void) close(*(int *) p);
gw_free(p);
}
static void server_shutdown(void)
{
gwlist_remove_producer(new_server_sockets);
if (server_thread_id != -1) {
gwthread_wakeup(server_thread_id);
gwthread_join_every(server_thread);
server_thread_is_running = 0;
}
mutex_destroy(server_thread_lock);
fdset_destroy(server_fdset);
server_fdset = NULL;
gwlist_destroy(new_server_sockets, destroy_struct_server);
gwlist_destroy(closed_server_sockets, destroy_int_pointer);
}
/***********************************************************************
* CGI variable manipulation.
*/
void http_destroy_cgiargs(List *args)
{
HTTPCGIVar *v;
gwlib_assert_init();
if (args == NULL)
return ;
while ((v = gwlist_extract_first(args)) != NULL) {
octstr_destroy(v->name);
octstr_destroy(v->value);
gw_free(v);
}
gwlist_destroy(args, NULL);
}
Octstr *http_cgi_variable(List *list, char *name)
{
int i;
HTTPCGIVar *v;
gwlib_assert_init();
gw_assert(list != NULL);
gw_assert(name != NULL);
for (i = 0; i < gwlist_len(list); ++i) {
v = gwlist_get(list, i);
if (octstr_str_compare(v->name, name) == 0)
return v->value;
}
return NULL;
}
/***********************************************************************
* Header manipulation.
*/
static int header_is_called(Octstr *header, char *name)
{
long colon;
colon = octstr_search_char(header, ':', 0);
if (colon == -1)
return 0;
if ((long) strlen(name) != colon)
return 0;
return strncasecmp(octstr_get_cstr(header), name, colon) == 0;
}
List *http_create_empty_headers(void)
{
gwlib_assert_init();
return gwlist_create();
}
void http_destroy_headers(List *headers)
{
gwlib_assert_init();
gwlist_destroy(headers, octstr_destroy_item);
}
void http_header_add(List *headers, char *name, char *contents)
{
gwlib_assert_init();
gw_assert(headers != NULL);
gw_assert(name != NULL);
gw_assert(contents != NULL);
gwlist_append(headers, octstr_format("%s: %s", name, contents));
}
/*
* Given an headers list and a position, returns its header name and value,
* or (X-Unknown, header) if it doesn't exist or if it's malformed - missing
* ":" for example
*/
void http_header_get(List *headers, long i, Octstr **name, Octstr **value)
{
Octstr *os;
long colon;
gwlib_assert_init();
gw_assert(i >= 0);
gw_assert(name != NULL);
gw_assert(value != NULL);
os = gwlist_get(headers, i);
if (os == NULL)
colon = -1;
else
colon = octstr_search_char(os, ':', 0);
if (colon == -1) {
error(0, "HTTP: Header does not contain a colon. BAD.");
*name = octstr_create("X-Unknown");
*value = octstr_duplicate(os);
} else {
*name = octstr_copy(os, 0, colon);
*value = octstr_copy(os, colon + 1, octstr_len(os));
octstr_strip_blanks(*value);
}
}
/*
* Given an headers list and a name, returns its value or NULL if it
* doesn't exist
*/
Octstr *http_header_value(List *headers, Octstr *name)
{
Octstr *value;
long i;
Octstr *os;
long colon;
Octstr *current_name;
gwlib_assert_init();
gw_assert(name);
value = NULL;
i = 0;
while (i < gwlist_len(headers)) {
os = gwlist_get(headers, i);
if (os == NULL)
colon = -1;
else
colon = octstr_search_char(os, ':', 0);
if (colon == -1) {
return NULL;
} else {
current_name = octstr_copy(os, 0, colon);
}
if (octstr_case_compare(current_name, name) == 0) {
value = octstr_copy(os, colon + 1, octstr_len(os));
octstr_strip_blanks(value);
octstr_destroy(current_name);
return value;
}
octstr_destroy(current_name);
++i;
}
return NULL;
}
List *http_header_duplicate(List *headers)
{
List *new;
long i, len;
gwlib_assert_init();
if (headers == NULL)
return NULL;
new = http_create_empty_headers();
len = gwlist_len(headers);
for (i = 0; i < len; ++i)
gwlist_append(new, octstr_duplicate(gwlist_get(headers, i)));
return new;
}
#define MAX_HEADER_LENGTH 256
/*
* Aggregate header in one (or more) lines with several parameters separated
* by commas, instead of one header per parameter
*/
void http_header_pack(List *headers)
{
Octstr *name, *value;
Octstr *name2, *value2;
long i, j;
gwlib_assert_init();
gw_assert(headers != NULL);
/*
* For each header, search forward headers for similar ones and if possible,
* add it to current header and delete it
*/
for(i = 0; i < gwlist_len(headers); i++) {
http_header_get(headers, i, &name, &value);
/* debug("http_header_pack", 0, "HTTP_HEADER_PACK: Processing header %d. [%s: %s]",
i, octstr_get_cstr(name), octstr_get_cstr(value)); */
for(j=i+1; j < gwlist_len(headers); j++) {
http_header_get(headers, j, &name2, &value2);
if(octstr_case_compare(name, name2) == 0) {
if(octstr_len(value) + 2 + octstr_len(value2) > MAX_HEADER_LENGTH) {
octstr_destroy(name2);
octstr_destroy(value2);
break;
} else {
Octstr *header;
/* Delete old header */
header = gwlist_get(headers, i);
octstr_destroy(header);
gwlist_delete(headers, i, 1);
/* Adds comma and new value to old header value */
octstr_append(value, octstr_imm(", "));
octstr_append(value, value2);
/* Creates a new header */
header = octstr_create("");
octstr_append(header, name);
octstr_append(header, octstr_imm(": "));
octstr_append(header, value);
gwlist_insert(headers, i, header);
/* Delete this header */
header = gwlist_get(headers, j);
octstr_destroy(header);
gwlist_delete(headers, j, 1);
j--;
}
}
octstr_destroy(name2);
octstr_destroy(value2);
}
octstr_destroy(name);
octstr_destroy(value);
}
}
void http_append_headers(List *to, List *from)
{
Octstr *header;
long i;
gwlib_assert_init();
gw_assert(to != NULL);
gw_assert(from != NULL);
for (i = 0; i < gwlist_len(from); ++i) {
header = gwlist_get(from, i);
gwlist_append(to, octstr_duplicate(header));
}
}
void http_header_combine(List *old_headers, List *new_headers)
{
long i;
Octstr *name;
Octstr *value;
/*
* Avoid doing this scan if old_headers is empty anyway.
*/
if (gwlist_len(old_headers) > 0) {
for (i = 0; i < gwlist_len(new_headers); i++) {
http_header_get(new_headers, i, &name, &value);
http_header_remove_all(old_headers, octstr_get_cstr(name));
octstr_destroy(name);
octstr_destroy(value);
}
}
http_append_headers(old_headers, new_headers);
}
Octstr *http_header_find_first_real(List *headers, char *name, const char *file, long line,
const char *func)
{
long i, name_len;
Octstr *h, *value;
gwlib_assert_init();
gw_assert(headers != NULL);
gw_assert(name != NULL);
name_len = strlen(name);
for (i = 0; i < gwlist_len(headers); ++i) {
h = gwlist_get(headers, i);
if (header_is_called(h, name)) {
value = octstr_copy_real(h, name_len + 1, octstr_len(h),
file, line, func);
octstr_strip_blanks(value);
return value;
}
}
return NULL;
}
List *http_header_find_all(List *headers, char *name)
{
List *list;
long i;
Octstr *h;
gwlib_assert_init();
gw_assert(headers != NULL);
gw_assert(name != NULL);
list = gwlist_create();
for (i = 0; i < gwlist_len(headers); ++i) {
h = gwlist_get(headers, i);
if (header_is_called(h, name))
gwlist_append(list, octstr_duplicate(h));
}
return list;
}
long http_header_remove_all(List *headers, char *name)
{
long i;
Octstr *h;
long count;
gwlib_assert_init();
gw_assert(headers != NULL);
gw_assert(name != NULL);
i = 0;
count = 0;
while (i < gwlist_len(headers)) {
h = gwlist_get(headers, i);
if (header_is_called(h, name)) {
gwlist_delete(headers, i, 1);
octstr_destroy(h);
count++;
} else
i++;
}
return count;
}
void http_remove_hop_headers(List *headers)
{
Octstr *h;
List *connection_headers;
gwlib_assert_init();
gw_assert(headers != NULL);
/*
* The hop-by-hop headers are a standard list, plus those named
* in the Connection header(s).
*/
connection_headers = http_header_find_all(headers, "Connection");
while ((h = gwlist_consume(connection_headers))) {
List *hop_headers;
Octstr *e;
octstr_delete(h, 0, strlen("Connection:"));
hop_headers = http_header_split_value(h);
octstr_destroy(h);
while ((e = gwlist_consume(hop_headers))) {
http_header_remove_all(headers, octstr_get_cstr(e));
octstr_destroy(e);
}
gwlist_destroy(hop_headers, NULL);
}
gwlist_destroy(connection_headers, NULL);
http_header_remove_all(headers, "Connection");
http_header_remove_all(headers, "Keep-Alive");
http_header_remove_all(headers, "Proxy-Authenticate");
http_header_remove_all(headers, "Proxy-Authorization");
http_header_remove_all(headers, "TE");
http_header_remove_all(headers, "Trailers");
http_header_remove_all(headers, "Transfer-Encoding");
http_header_remove_all(headers, "Upgrade");
}
void http_header_mark_transformation(List *headers,
Octstr *new_body, Octstr *new_type)
{
Octstr *new_length = NULL;
/* Remove all headers that no longer apply to the new body. */
http_header_remove_all(headers, "Content-Length");
http_header_remove_all(headers, "Content-MD5");
http_header_remove_all(headers, "Content-Type");
/* Add headers that we need to describe the new body. */
new_length = octstr_format("%ld", octstr_len(new_body));
http_header_add(headers, "Content-Length", octstr_get_cstr(new_length));
if(octstr_len(new_type))
http_header_add(headers, "Content-Type", octstr_get_cstr(new_type));
/* Perhaps we should add Warning: 214 "Transformation applied" too? */
octstr_destroy(new_length);
}
void http_header_get_content_type(List *headers, Octstr **type,
Octstr **charset)
{
Octstr *h;
long semicolon, equals, len;
gwlib_assert_init();
gw_assert(headers != NULL);
gw_assert(type != NULL);
gw_assert(charset != NULL);
h = http_header_find_first(headers, "Content-Type");
if (h == NULL) {
*type = octstr_create("application/octet-stream");
*charset = octstr_create("");
} else {
octstr_strip_blanks(h);
semicolon = octstr_search_char(h, ';', 0);
if (semicolon == -1) {
*type = h;
*charset = octstr_create("");
} else {
*charset = octstr_duplicate(h);
octstr_delete(*charset, 0, semicolon + 1);
octstr_strip_blanks(*charset);
equals = octstr_search_char(*charset, '=', 0);
if (equals == -1)
octstr_truncate(*charset, 0);
else {
octstr_delete(*charset, 0, equals + 1);
if (octstr_get_char(*charset, 0) == '"')
octstr_delete(*charset, 0, 1);
len = octstr_len(*charset);
if (octstr_get_char(*charset, len - 1) == '"')
octstr_truncate(*charset, len - 1);
}
octstr_truncate(h, semicolon);
octstr_strip_blanks(h);
*type = h;
}
/*
* According to HTTP/1.1 (RFC 2616, section 3.7.1) we have to ensure
* to return charset 'iso-8859-1' in case of no given encoding and
* content-type is a 'text' subtype.
*/
if (octstr_len(*charset) == 0 &&
octstr_ncompare(*type, octstr_imm("text"), 4) == 0)
octstr_append_cstr(*charset, "ISO-8859-1");
}
}
static void http_header_add_element(List *list, Octstr *value,
long start, long end)
{
Octstr *element;
element = octstr_copy(value, start, end - start);
octstr_strip_blanks(element);
if (octstr_len(element) == 0)
octstr_destroy(element);
else
gwlist_append(list, element);
}
long http_header_quoted_string_len(Octstr *header, long start)
{
long len;
long pos;
int c;
if (octstr_get_char(header, start) != '"')
return -1;
len = octstr_len(header);
for (pos = start + 1; pos < len; pos++) {
c = octstr_get_char(header, pos);
if (c == '\\') /* quoted-pair */
pos++;
else if (c == '"')
return pos - start + 1;
}
warning(0, "Header contains unterminated quoted-string:");
warning(0, "%s", octstr_get_cstr(header));
return len - start;
}
List *http_header_split_value(Octstr *value)
{
long start; /* start of current element */
long pos;
long len;
List *result;
int c;
/*
* According to RFC2616 section 4.2, a field-value is either *TEXT
* (the caller is responsible for not feeding us one of those) or
* combinations of token, separators, and quoted-string. We're
* looking for commas which are separators, and have to skip
* commas in quoted-strings.
*/
result = gwlist_create();
len = octstr_len(value);
start = 0;
for (pos = 0; pos < len; pos++) {
c = octstr_get_char(value, pos);
if (c == ',') {
http_header_add_element(result, value, start, pos);
start = pos + 1;
} else if (c == '"') {
pos += http_header_quoted_string_len(value, pos);
pos--; /* compensate for the loop's pos++ */
}
}
http_header_add_element(result, value, start, len);
return result;
}
List *http_header_split_auth_value(Octstr *value)
{
List *result;
Octstr *auth_scheme;
Octstr *element;
long i;
/*
* According to RFC2617, both "challenge" and "credentials"
* consist of an auth-scheme followed by a list of auth-param.
* Since we have to parse a list of challenges or credentials,
* we have to look for auth-scheme to signal the start of
* a new element. (We can't just split on commas because
* they are also used to separate the auth-params.)
*
* An auth-scheme is a single token, while an auth-param is
* always a key=value pair. So we can recognize an auth-scheme
* as a token that is not followed by a '=' sign.
*
* Simple approach: First split at all commas, then recombine
* the elements that belong to the same challenge or credential.
* This is somewhat expensive but saves programmer thinking time.
*
* Richard Braakman
*/
result = http_header_split_value(value);
if (gwlist_len(result) == 0)
return result;
auth_scheme = gwlist_get(result, 0);
i = 1;
while (i < gwlist_len(result)) {
int c;
long pos;
element = gwlist_get(result, i);
/*
* If the element starts with: token '='
* then it's just an auth_param; append it to the current
* auth_scheme. If it starts with: token token '='
* then it's the start of a new auth scheme.
*
* To make the scan easier, we consider anything other
* than whitespace or '=' to be part of a token.
*/
/* Skip first token */
for (pos = 0; pos < octstr_len(element); pos++) {
c = octstr_get_char(element, pos);
if (isspace(c) || c == '=')
break;
}
/* Skip whitespace, if any */
while (isspace(octstr_get_char(element, pos)))
pos++;
if (octstr_get_char(element, pos) == '=') {
octstr_append_char(auth_scheme, ';');
octstr_append(auth_scheme, element);
gwlist_delete(result, i, 1);
octstr_destroy(element);
} else {
char semicolon = ';';
octstr_insert_data(element, pos, &semicolon, 1);
auth_scheme = element;
i++;
}
}
return result;
}
void http_header_dump(List *headers)
{
long i;
gwlib_assert_init();
debug("gwlib.http", 0, "Dumping HTTP headers:");
for (i = 0; headers != NULL && i < gwlist_len(headers); ++i)
octstr_dump(gwlist_get(headers, i), 1);
debug("gwlib.http", 0, "End of dump.");
}
void http_cgivar_dump(List *cgiargs)
{
HTTPCGIVar *v;
long i, len;
gwlib_assert_init();
len = gwlist_len(cgiargs);
debug("gwlib.http", 0, "Dumping %ld cgi variables:", len);
for (i = 0; i < len; i++) {
v = gwlist_get(cgiargs, i);
octstr_dump(v->name, 0);
octstr_dump(v->value, 0);
}
debug("gwlib.http", 0, "End of dump.");
}
static int http_something_accepted(List *headers, char *header_name,
char *what)
{
int found;
long i;
List *accepts;
Octstr *needle = octstr_create(what);
gwlib_assert_init();
gw_assert(headers != NULL);
gw_assert(what != NULL);
/* return all headers with this name */
accepts = http_header_find_all(headers, header_name);
found = 0;
for (i = 0; !found && i < gwlist_len(accepts); ++i) {
Octstr *header_value = gwlist_get(accepts, i);
if (octstr_case_search(header_value, needle, 0) != -1)
found = 1;
}
octstr_destroy(needle);
http_destroy_headers(accepts);
return found;
}
int http_type_accepted(List *headers, char *type)
{
return http_something_accepted(headers, "Accept", type);
}
int http_charset_accepted(List *headers, char *charset)
{
return http_something_accepted(headers, "Accept-Charset", charset);
}
void http_add_basic_auth(List *headers, Octstr *username, Octstr *password)
{
Octstr *os;
if (password != NULL)
os = octstr_format("%S:%S", username, password);
else
os = octstr_format("%S", username);
octstr_binary_to_base64(os);
octstr_strip_blanks(os);
octstr_insert(os, octstr_imm("Basic "), 0);
http_header_add(headers, "Authorization", octstr_get_cstr(os));
octstr_destroy(os);
}
Octstr *http_get_header_parameter(Octstr *value, Octstr *parameter)
{
long pos, len, end;
int c, found = 0;
Octstr *result = NULL;
len = octstr_len(value);
/* Find the start of the first parameter. */
for (pos = 0; pos < len; pos++) {
c = octstr_get_char(value, pos);
if (c == ';')
break;
else if (c == '"')
pos += http_header_quoted_string_len(value, pos) - 1;
}
if (pos >= len)
return NULL; /* no parameters */
for (pos++; pos > 0 && pos < len && found == 0; pos++) {
Octstr *key = NULL;
Octstr *val = NULL;
end = octstr_search_char(value, '=', pos);
if (end < 0)
end = octstr_search_char(value, ';', pos);
if (end < 0)
end = octstr_len(value);
key = octstr_copy(value, pos, end - pos);
octstr_strip_blanks(key);
pos = end;
if (octstr_get_char(value, pos) == '=') {
pos++;
while (isspace(octstr_get_char(value, pos)))
pos++;
if (octstr_get_char(value, pos) == '"')
end = pos + http_header_quoted_string_len(value, pos);
else
end = octstr_search_char(value, ';', pos);
if (end < 0)
end = octstr_len(value);
val = octstr_copy(value, pos, end - pos);
octstr_strip_blanks(val);
pos = end;
pos = octstr_search_char(value, ';', pos);
}
/* is this the pair we look for? bail out then*/
if (octstr_compare(key, parameter) == 0) {
found++;
result = octstr_duplicate(val);
}
octstr_destroy(key);
octstr_destroy(val);
}
return result;
}
/***********************************************************************
* Module initialization and shutdown.
*/
void http_init(void)
{
gw_assert(run_status == limbo);
#ifdef HAVE_LIBSSL
openssl_init_locks();
conn_init_ssl();
#endif /* HAVE_LIBSSL */
proxy_init();
client_init();
conn_pool_init();
port_init();
server_init();
#ifdef HAVE_LIBSSL
server_ssl_init();
#endif /* HAVE_LIBSSL */
run_status = running;
}
void http_shutdown(void)
{
gwlib_assert_init();
gw_assert(run_status == running);
run_status = terminating;
conn_pool_shutdown();
client_shutdown();
server_shutdown();
port_shutdown();
proxy_shutdown();
#ifdef HAVE_LIBSSL
openssl_shutdown_locks();
conn_shutdown_ssl();
server_shutdown_ssl();
#endif /* HAVE_LIBSSL */
run_status = limbo;
}
/*
* This function relies on the HTTP_STATUS_* enum values being
* chosen to fit this.
*/
int http_status_class(int code)
{
int sclass;
if (code < 100 || code >= 600)
sclass = HTTP_STATUS_UNKNOWN;
else
sclass = code - (code % 100);
return sclass;
}
int http_name2method(Octstr *method)
{
gw_assert(method != NULL);
if (octstr_str_compare(method, "GET") == 0) {
return HTTP_METHOD_GET;
}
else if (octstr_str_compare(method, "POST") == 0) {
return HTTP_METHOD_POST;
}
else if (octstr_str_compare(method, "HEAD") == 0) {
return HTTP_METHOD_HEAD;
}
return -1;
}
char *http_method2name(int method)
{
gw_assert(method > 0 && method <= 3);
return http_methods[method-1];
}