Logo Search packages:      
Sourcecode: kannel version File versions  Download package

http.c

/* ==================================================================== 
 * The Kannel Software License, Version 1.0 
 * 
 * Copyright (c) 2001-2004 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 <http://www.kannel.org/>. 
 * 
 * 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 <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "gwlib.h"

/* comment this out if you don't want Keep-Alive HTTP requests */
#define USE_KEEPALIVE 1

/* comment this out if you don't want HTTP responses to be dumped */
#define DUMP_RESPONSE 1

/***********************************************************************
 * 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 (list_len(headers) == 0)
        prev = NULL;
    else
      prev = list_get(headers, list_len(headers) - 1);

    for (;;) {
      line = conn_read_line(conn);
      if (line == NULL) {
          if (conn_eof(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 {
            list_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 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 = list_create();
}


static void proxy_shutdown(void)
{
    http_close_proxy();
    mutex_destroy(proxy_mutex);
    proxy_mutex = NULL;
}


static int proxy_used_for_host(Octstr *host)
{
    int i;

    mutex_lock(proxy_mutex);

    if (proxy_hostname == NULL) {
        mutex_unlock(proxy_mutex);
        return 0;
    }

    for (i = 0; i < list_len(proxy_exceptions); ++i) {
        if (octstr_compare(host, list_get(proxy_exceptions, i)) == 0) {
            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 *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 = list_create();
    for (i = 0; i < list_len(exceptions); ++i) {
        e = list_get(exceptions, i);
      debug("gwlib.http", 0, "HTTP: Proxy exception `%s'.",
            octstr_get_cstr(e));
        list_append(proxy_exceptions, octstr_duplicate(e));
    }
    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;
    list_destroy(proxy_exceptions, octstr_destroy_item);
    proxy_exceptions = 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) {
          error(0, "HTTP: Content-Length header wrong: <%s>",
              octstr_get_cstr(h));
          ent->state = body_error;
        } 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)
        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.
 */


/*
 * Maximum number of HTTP redirections to follow. Making this infinite
 * could cause infinite looping if the redirections loop.
 */
enum { 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 retrying;
    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->retrying = 0;
    trans->follow_remaining = follow_remaining;
    trans->certkeyfile = 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 = NULL;
static Mutex *conn_pool_lock = NULL;


static void conn_pool_item_destroy(void *item)
{
    Connection *conn;
    
    while ((conn = list_extract_first(item)) != NULL)
      conn_destroy(conn);
    list_destroy(item, NULL);
}

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 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;
    Connection *conn;

    mutex_lock(conn_pool_lock);
    key = conn_pool_key(host, port);
    list = dict_get(conn_pool, key);
    octstr_destroy(key);
    if (list == NULL)
      conn = NULL;
    else {
      while (1) {
          conn = list_extract_first(list);
          if (conn == NULL)
            break;
          /* 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))
            break;
          conn_destroy(conn);
      }
    }
    mutex_unlock(conn_pool_lock);
    
    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 conn_pool_put(Connection *conn, Octstr *host, int port)
{
    Octstr *key;
    List *list;
    
    mutex_lock(conn_pool_lock);
    key = conn_pool_key(host, port);
    list = dict_get(conn_pool, key);
    if (list == NULL) {
      list = list_create();
        dict_put(conn_pool, key, list);
    }
    list_append(list, conn);
    octstr_destroy(key);
    mutex_unlock(conn_pool_lock);
}
#endif


/*
 * 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;


HTTPCaller *http_caller_create(void)
{
    HTTPCaller *caller;
    
    caller = list_create();
    list_add_producer(caller);
    return caller;
}


void http_caller_destroy(HTTPCaller *caller)
{
    list_destroy(caller, server_destroy);
}


void http_caller_signal_shutdown(HTTPCaller *caller)
{
    list_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;
    char buf[128];
    
    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");
            conn_unregister(conn);
            goto error;
          }

          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");
            sprintf(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);
          }

          if ((rc = send_request(trans)) == 0) {
            trans->state = reading_status;
            conn_register(trans->conn, client_fdset, handle_transaction, 
                          trans);
          } 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, so we close the connection and try again, 
             * opening a new socket, but only once.
             */
            if (trans->retrying) {
                    debug("gwlib.http",0,"Failed while retrying");
                goto error;
            } else {
                    /* implicit conn_unregister */
                conn_destroy(trans->conn);
                trans->conn = NULL;
                trans->retrying = 1;
                trans->state = request_not_sent;
                list_produce(pending_requests, trans);
                return;
            }
          } 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("wsp.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))
            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 = list_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 */
        list_produce(pending_requests, trans);

    } else {
        /* handle this response as usual */
        list_produce(trans->caller, trans);
    }
    return;

error:
    conn_destroy(trans->conn);
    trans->conn = NULL;
    error(0, "Couldn't fetch <%s>", octstr_get_cstr(trans->url));
    trans->status = -1;
    list_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 < list_len(headers); ++i) {
        octstr_append(request, list_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 < list_len(headers); ++i) {
        octstr_append(response, list_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(&(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(&(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;
    Octstr *host;
    HTTPURLParse *p;
    int port;

    conn = NULL;

    /* 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)) {
        host = proxy_hostname;
        port = proxy_port;
    } else {
        host = trans->host;
        port = trans->port;
    }

    if (trans->retrying) {
#ifdef HAVE_LIBSSL
    if (trans->ssl) conn = conn_open_ssl(host, port, trans->certkeyfile, http_interface);
        else
#endif /* HAVE_LIBSSL */
      conn = conn_open_tcp_nb(host, port, http_interface);
            debug("gwlib.http", 0, "HTTP: Opening NEW connection to `%s:%d' (fd=%d).",
                  octstr_get_cstr(host), port, conn_get_id(conn));
    } else
    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 socket from which the
 * response can be read or -1 for error.
 */
static int send_request(HTTPServer *trans)
{
  Octstr *request;

  request = NULL;

  /* 
   * 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)) {
    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("wsp.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;
    char buf[128];    
    int rc;

    while (run_status == running) {
        trans = list_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)
        list_produce(trans->caller, trans);
        else {
          if (conn_is_connected(trans->conn) == 0) {
          debug("gwlib.http", 0, "Socket connected at once");

        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");
            sprintf(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);
        }
          if ((rc = send_request(trans)) == 0) {
            trans->state = reading_status;
              conn_register(trans->conn, client_fdset, handle_transaction, 
                            trans);
            } else {
              list_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();
          gwthread_create(write_request_thread, NULL);
          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;
    list_produce(pending_requests, trans);
    start_client_threads();
}


void *http_receive_result(HTTPCaller *caller, int *status, Octstr **final_url,
                   List **headers, Octstr **body)
{
    HTTPServer *trans;
    void *request_id;

    trans = list_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 = list_create();
    list_add_producer(pending_requests);
    client_thread_lock = mutex_create();
}


static void client_shutdown(void)
{
    list_remove_producer(pending_requests);
    gwthread_join_every(write_request_thread);
    list_destroy(pending_requests, server_destroy);
    mutex_destroy(client_thread_lock);
    fdset_destroy(client_fdset);
    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;
};


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;
    return p;
}


static void client_destroy(void *client)
{
    HTTPClient *p;
    
    if (client == NULL)
      return;

    p = client;
    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 void port_init(void)
{
    port_mutex = mutex_create();
    port_collection = dict_create(1024, NULL);
}

static void port_shutdown(void)
{
    mutex_destroy(port_mutex);
    dict_destroy(port_collection);
}


static Octstr *port_key(int port)
{
    return octstr_format("%d", port);
}


static void port_add(int port)
{
    Octstr *key;
    struct port *p;

    p = gw_malloc(sizeof(*p));
    p->clients_with_requests = list_create();
    list_add_producer(p->clients_with_requests);
    p->active_consumers = counter_create();

    key = port_key(port);
    mutex_lock(port_mutex);
    dict_put(port_collection, key, p);
    mutex_unlock(port_mutex);
    octstr_destroy(key);
}


static void port_remove(int port)
{
    Octstr *key;
    struct port *p;

    key = port_key(port);
    mutex_lock(port_mutex);
    p = dict_remove(port_collection, key);
    mutex_unlock(port_mutex);
    octstr_destroy(key);

    list_remove_producer(p->clients_with_requests);
    while (counter_value(p->active_consumers) > 0)
       gwthread_sleep(0.1);    /* Reasonable use of busy waiting. */
    list_destroy(p->clients_with_requests, client_destroy);
    counter_destroy(p->active_consumers);
    gw_free(p);
}


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);
    gw_assert(p != NULL);
    list_produce(p->clients_with_requests, client);
    octstr_destroy(key);
    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 = list_consume(p->clients_with_requests);
       counter_decrease(p->active_consumers);
    }
    return client;
}


/*
 * Maximum number of servers (ports) we have open at the same time.
 */
enum { 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 (list_len(words) != 3) {
        list_destroy(words, octstr_destroy_item);
      return -1;
    }

    method_str = list_get(words, 0);
    *url = list_get(words, 1);
    version = list_get(words, 2);
    list_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);
          if (ret == -1)
            goto error;
          /*
           * 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:
        /* Implicite 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) {
            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:
    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;
    int addrlen;
    Connection *conn;
    HTTPClient *client;
    int ret;

    n = 0;
    while (run_status == running && keep_servers_open) {

        if (n == 0 || (n < MAX_SERVERS && list_len(new_server_sockets) > 0)) {
            p = list_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(0, "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.");
                    (void) close(tab[i].fd);
                    port_remove(ports[i]);
                    tab[i].fd = -1;
                    ports[i] = -1;
                    ssl[i] = 0;
                } 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 = list_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();
          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);
    list_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;
    list_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);
        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 list_create();

    args = octstr_copy(url, query + 1, octstr_len(url));
    octstr_truncate(url, query);

    list = list_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);

        list_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 < list_len(headers); ++i)
      octstr_format_append(response, "%S\r\n", list_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 = list_create();
    list_add_producer(new_server_sockets);
    closed_server_sockets = list_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)
{
    list_remove_producer(new_server_sockets);
    if (server_thread_id != -1) {
      gwthread_wakeup(server_thread_id);
      gwthread_join_every(server_thread);
    }
    mutex_destroy(server_thread_lock);
    fdset_destroy(server_fdset);
    list_destroy(new_server_sockets, destroy_struct_server);
    list_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 = list_extract_first(args)) != NULL) {
        octstr_destroy(v->name);
        octstr_destroy(v->value);
        gw_free(v);
    }
    list_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 < list_len(list); ++i) {
        v = list_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 list_create();
}


void http_destroy_headers(List *headers)
{
    gwlib_assert_init();
    list_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);

    list_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 = list_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 < list_len(headers)) {
        os = list_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;

    gwlib_assert_init();

    if (headers == NULL)
        return NULL;

    new = http_create_empty_headers();
    for (i = 0; i < list_len(headers); ++i)
        list_append(new, octstr_duplicate(list_get(headers, i)));
    return new;
}


#define MAX_HEADER_LENGHT 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 < list_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 < list_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_LENGHT) {
                octstr_destroy(name2);
                octstr_destroy(value2);
                    break;
                } else {
                Octstr *header;

                /* Delete old header */
                header = list_get(headers, i);
                octstr_destroy(header);
                    list_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);
                    list_insert(headers, i, header);

                /* Delete this header */
                header = list_get(headers, j);
                octstr_destroy(header);
                    list_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 < list_len(from); ++i) {
        header = list_get(from, i);
        list_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 (list_len(old_headers) > 0) {
        for (i = 0; i < list_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 < list_len(headers); ++i) {
        h = list_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 = list_create();
    for (i = 0; i < list_len(headers); ++i) {
        h = list_get(headers, i);
        if (header_is_called(h, name))
            list_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 < list_len(headers)) {
      h = list_get(headers, i);
      if (header_is_called(h, name)) {
          list_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 = list_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 = list_consume(hop_headers))) {
          http_header_remove_all(headers, octstr_get_cstr(e));
          octstr_destroy(e);
      }

      list_destroy(hop_headers, NULL);
    }
    list_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;
        }
    }
}


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
      list_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 = list_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 (list_len(result) == 0)
        return result;

    auth_scheme = list_get(result, 0);
    i = 1;
    while (i < list_len(result)) {
        int c;
        long pos;

        element = list_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);
            list_delete(result, i, 1);
            octstr_destroy(element);
        } else {
            unsigned 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 < list_len(headers); ++i)
        octstr_dump(list_get(headers, i), 1);
    debug("gwlib.http", 0, "End of dump.");
}

void http_cgivar_dump(List *cgiargs)
{
    HTTPCGIVar *v;

    gwlib_assert_init();

    debug("gwlib.http", 0, "Dumping %ld cgi variables:", list_len(cgiargs));
    while ((v = list_extract_first(cgiargs)) != NULL) {
        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 < list_len(accepts); ++i) {
        Octstr *header_value = list_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();
    server_init();
#ifdef HAVE_LIBSSL
    server_ssl_init();
#endif /* HAVE_LIBSSL */
    port_init();
    
    run_status = running;
}


void http_shutdown(void)
{
    gwlib_assert_init();
    gw_assert(run_status == running);

    run_status = terminating;

    conn_pool_shutdown();
    port_shutdown();
    client_shutdown();
    server_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];
}


Generated by  Doxygen 1.6.0   Back to index