/***************************************************************************
 *
 * Copyright (c) 2000 BalaBit IT Ltd, Budapest, Hungary
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: http.c,v 1.40.2.6.2.4 2001/09/19 19:34:36 bazsi Exp $
 * 
 * Author: Balazs Scheidler <bazsi@balabit.hu>
 * Auditor: 
 * Last audited version: 
 * Notes:
 *   Based on the code by: Viktor Peter Kovacs <vps__@freemail.hu>
 *   
 ***************************************************************************/

#include "http.h"

#include <zorp/thread.h>
#include <zorp/registry.h>
#include <zorp/log.h>
#include <zorp/policy.h>
#include <zorp/authorization.h>

#include <ctype.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>

#define HTTP_DEBUG "http.debug"
#define HTTP_ERROR "http.error"
#define HTTP_POLICY "http.policy"
#define HTTP_REQUEST "http.request"
#define HTTP_RESPONSE "http.response"

/* case insensitive compare */
static int
http_filter_hash_compare(gconstpointer a, gconstpointer b)
{
  if (strcasecmp((char *) a, (char *) b) == 0)
    {
      return 1;
    }
  return 0;
}

static int
http_filter_hash_bucket(gconstpointer a)
{
  int sum = 0;
  char *s = (char *) a;
  
  while (*s != 0)
    {
      sum += toupper(*s);     
      s++;
    }
  return (sum % 16);          
}

static void
http_config_set_defaults(HttpProxy *self)
{
  z_proxy_enter(self);
  
  self->connection_mode = HTTP_CONNECTION_CLOSE;
  self->transparent_mode = TRUE;
  self->permit_server_requests = TRUE;
  self->permit_proxy_requests = FALSE;

  self->rewrite_host_header = TRUE;
  self->require_host_header = TRUE;
  self->strict_header_checking = TRUE;
  self->permit_null_response = TRUE;
  self->max_line_length = 4096;
  self->max_url_length = 4096;
  self->max_header_lines = 50;
  self->max_hostname_length = 256;
  self->timeout_request = 10000;
  self->timeout = 300000;
  self->default_port = 80;

  self->headers[EP_CLIENT] = g_string_sized_new(256);
  self->headers[EP_SERVER] = g_string_sized_new(256);

  self->request_url = g_string_sized_new(128);
  self->current_header_name = g_string_sized_new(16);
  self->current_header_value = g_string_sized_new(32);
  
  self->url_proto = g_string_sized_new(0);
  self->url_username = g_string_sized_new(0);
  self->url_passwd = g_string_sized_new(0);
  self->url_host = g_string_sized_new(0);
  self->url_port = 80;
  self->url_file = g_string_sized_new(0);

  self->parent_proxy = g_string_sized_new(0);
  self->parent_proxy_port = 3128;
  
  self->target_port_range = g_string_new("80,443");

  self->request_method_policy =
    g_hash_table_new((GHashFunc) http_filter_hash_bucket,
                     (GCompareFunc) http_filter_hash_compare);
  self->request_header_policy =
    g_hash_table_new((GHashFunc) http_filter_hash_bucket,
                     (GCompareFunc) http_filter_hash_compare);
#if 0
  self->response_policy =
    g_hash_table_new((GHashFunc) http_filter_hash_bucket,
                     (GCompareFunc) http_filter_hash_compare);
#endif

  self->response_policy = 
    z_dim_hash_table_new(1, 2, DIMHASH_WILDCARD, DIMHASH_CONSUME);
  self->response_header_policy =
    g_hash_table_new((GHashFunc) http_filter_hash_bucket,
                     (GCompareFunc) http_filter_hash_compare);


  self->error_info = g_string_sized_new(0);
  self->error_headers = g_string_sized_new(0);
  self->error_code = -1;
  self->error_status = 500;
  
  self->auth_inband_supported = 1;
  self->auth_realm = g_string_new("Zorp HTTP auth");
  self->old_auth_header = g_string_sized_new(0);
  z_proxy_leave(self);
}

static void
http_config_init(HttpProxy *self)
{
  z_proxy_enter(self);
  if (self->max_line_length > HTTP_MAX_LINE)
    self->max_line_length = HTTP_MAX_LINE;
  self->super.endpoints[EP_CLIENT]->timeout = self->timeout_request;
  z_proxy_leave(self);
}

static void
http_register_vars(HttpProxy *self)
{
  z_proxy_enter(self);
  
  z_proxy_var_new(&self->super, "transparent_mode",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->transparent_mode);

  z_proxy_var_new(&self->super, "permit_server_requests",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->permit_server_requests);

  z_proxy_var_new(&self->super, "permit_null_response",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->permit_null_response);

  z_proxy_var_new(&self->super, "permit_proxy_requests",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->permit_proxy_requests);

  /* close or keep-alive */
  z_proxy_var_new(&self->super, "connection_mode", 
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET,
                  &self->connection_mode);

  /* string containing parent proxy */
  z_proxy_var_new(&self->super, "parent_proxy",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
		  self->parent_proxy);

  /* parent proxy port */
  z_proxy_var_new(&self->super, "parent_proxy_port",
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
		  &self->parent_proxy_port);

  /* default port if portnumber is not specified in urls */
  z_proxy_var_new(&self->super, "default_port",
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_SET_CONFIG,
		  &self->default_port);

  /* rewrite host header when redirecting */
  z_proxy_var_new(&self->super, "rewrite_host_header",
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
		  &self->rewrite_host_header);

  /* require host header */
  z_proxy_var_new(&self->super, "require_host_header",
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
		  &self->require_host_header);

  /* enable strict header checking */
  z_proxy_var_new(&self->super, "strict_header_checking",
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
		  &self->strict_header_checking);

  /* integer */
  z_proxy_var_new(&self->super, "max_line_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->max_line_length);

  /* integer */
  z_proxy_var_new(&self->super, "max_url_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->max_url_length);

  /* integer */
  z_proxy_var_new(&self->super, "max_hostname_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->max_hostname_length);

  /* integer */
  z_proxy_var_new(&self->super, "max_header_lines",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  &self->max_header_lines);

  /* integer */
  z_proxy_var_new(&self->super, "max_keepalive_requests",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_SET_CONFIG,
                  &self->max_keepalive_requests);

  /* integer */
  z_proxy_var_new(&self->super, "request_count",
                  Z_VAR_TYPE_INT | Z_VAR_GET,
                  &self->request_count);

  /* timeout value in milliseconds */
  z_proxy_var_new(&self->super, "timeout", 
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_GET_CONFIG | Z_VAR_SET_CONFIG,
                  &self->timeout);

  /* timeout value in milliseconds */
  z_proxy_var_new(&self->super, "timeout_request", 
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET | Z_VAR_GET_CONFIG | Z_VAR_SET_CONFIG,
                  &self->timeout_request);

  /* hash indexed by request method */
  z_proxy_var_new(&self->super, "request",
                  Z_VAR_TYPE_HASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  self->request_method_policy);

  /* hash indexed by header name */
  z_proxy_var_new(&self->super, "request_header",
                  Z_VAR_TYPE_HASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  self->request_header_policy);

  /* hash indexed by response code */
  z_proxy_var_new(&self->super, "response",
                  Z_VAR_TYPE_DIMHASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  self->response_policy);

  /* hash indexed by header name */
  z_proxy_var_new(&self->super, "response_header",
                  Z_VAR_TYPE_HASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  self->response_header_policy);

  /* string containing current url */
  z_proxy_var_new(&self->super, "request_url",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
		  self->request_url);

  /* string containing current url proto */
  z_proxy_var_new(&self->super, "request_url_proto",
		  Z_VAR_TYPE_STRING | Z_VAR_GET,
		  self->url_proto);

  /* string containing current url username */
  z_proxy_var_new(&self->super, "request_url_username",
		  Z_VAR_TYPE_STRING | Z_VAR_GET,
		  self->url_username);

  /* string containing current url passwd */
  z_proxy_var_new(&self->super, "request_url_passwd",
		  Z_VAR_TYPE_STRING | Z_VAR_GET,
		  self->url_passwd);

  /* string containing current url hostname */
  z_proxy_var_new(&self->super, "request_url_host",
		  Z_VAR_TYPE_STRING | Z_VAR_GET,
		  self->url_host);

  /* string containing current url port */
  z_proxy_var_new(&self->super, "request_url_port",
		  Z_VAR_TYPE_INT | Z_VAR_GET,
		  &self->url_port);

  /* string containing current url file */
  z_proxy_var_new(&self->super, "request_url_file",
		  Z_VAR_TYPE_STRING | Z_VAR_GET,
		  self->url_file);

  /* string containing current header name */
  z_proxy_var_new(&self->super, "current_header_name",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
		  self->current_header_name);

  /* string containing current header value */
  z_proxy_var_new(&self->super, "current_header_value",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
		  self->current_header_value);

  /* error response */
  z_proxy_var_new(&self->super, "error_status", 
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET,
                  &self->error_status);

  /* string inserted into error messages */
  z_proxy_var_new(&self->super, "error_info",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
		  self->error_info);

  /* inband authentication support */
  z_proxy_var_new(&self->super, "auth_inband_supported",
  		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_GET_CONFIG,
  		  &self->auth_inband_supported);

  z_proxy_var_new(&self->super, "auth",
  		  Z_VAR_TYPE_OBJECT | Z_VAR_GET | Z_VAR_SET_CONFIG,
  		  &self->auth);

  z_proxy_var_new(&self->super, "auth_realm",
  		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET_CONFIG,
  		  self->auth_realm);

  z_proxy_var_new(&self->super, "target_port_range", 
                  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET | Z_VAR_GET_CONFIG | Z_VAR_SET_CONFIG, 
                  self->target_port_range);

  /* compatibility with Zorp 0.8.x */
  z_proxy_var_new(&self->super, "transparent_server_requests",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_GET_CONFIG,
                  "permit_server_requests");  

  z_proxy_var_new(&self->super, "transparent_proxy_requests",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  "permit_proxy_requests");  

  z_proxy_var_new(&self->super, "request_timeout",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_SET | Z_VAR_GET_CONFIG | Z_VAR_SET_CONFIG,
		  "timeout_request");

  z_proxy_var_new(&self->super, "request_headers",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  "request_header");

  z_proxy_var_new(&self->super, "response_headers",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  "response_header");
  
  z_proxy_var_new(&self->super, "url_proto",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET,
		  "request_url_proto");

  z_proxy_var_new(&self->super, "url_username",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET,
		  "request_url_username");

  z_proxy_var_new(&self->super, "url_passwd",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET,
		  "request_url_passwd");

  z_proxy_var_new(&self->super, "url_host",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET,
		  "request_url_host");

  z_proxy_var_new(&self->super, "url_port",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET,
		  "request_url_port");

  z_proxy_var_new(&self->super, "url_file",
		  Z_VAR_TYPE_ALIAS | Z_VAR_GET,
		  "request_url_file");

  z_proxy_var_new(&self->super, "error_response",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_SET,
                  "error_status");

  z_proxy_leave(self);
}

static gboolean
http_error_message(HttpProxy *self, int response_code, int message_code, GString *infomsg)
{
  gchar *messages[] = 
    { 
      NULL, 
      "clientsyntax.html", 
      "serversyntax.html", 
      "policysyntax.html", 
      "policyviolation.html", 
      "invalidurl.html", 
      "connecterror.html", 
      "ioerror.html", 
      "auth.html",
      "clienttimeout.html",
      "servertimeout.html"
    };
  gchar response[64], filename[256];
  int fd;
 
  z_proxy_enter(self); 
  if (message_code < 0 || (message_code >= sizeof(messages) / sizeof(char *)))
    {
      /* message code out of range */
      z_proxy_log(self, HTTP_ERROR, 1, "Internal error, message code out of range. message_code=%d", message_code);
      z_proxy_leave(self);
      return FALSE;
    }
    
  if (message_code == 0)
    {
      z_proxy_leave(self);
      return TRUE;
    }
    
  if (self->proto_version >= 0x0100)
    {
      g_snprintf(response, sizeof(response), "HTTP/1.0 %d Proxy error encountered\r\n", response_code);
      
      if (!http_write(self, EP_CLIENT, response, strlen(response)))
        {
	/* error writing error message */
	  z_proxy_leave(self);
          return FALSE;
        }
      g_string_truncate(self->headers[EP_SERVER], 0);
      if (!self->transparent_mode)
        g_string_append(self->headers[EP_SERVER], "Proxy-Connection: close\r\n");
      else
        g_string_append(self->headers[EP_SERVER], "Connection: close\r\n");
      
      g_string_append(self->headers[EP_SERVER], self->error_headers->str);
      g_string_append(self->headers[EP_SERVER], "Content-Type: text/html\r\n");
      g_string_append(self->headers[EP_SERVER], "\r\n");
      if (!http_write(self, EP_CLIENT, self->headers[EP_SERVER]->str, self->headers[EP_SERVER]->len))
        {
          z_proxy_leave(self);
          return FALSE;
        }
    }
  g_snprintf(filename, sizeof(filename), "%s/http/%s", ZORP_DATADIR, messages[message_code]);
  z_proxy_log(self, HTTP_DEBUG, 5, "error, serving %s", filename);
  fd = open(filename, O_RDONLY);
  if (fd == -1)
    {
      z_proxy_log(self, HTTP_ERROR, 3, "error opening error file %s", filename);
      http_write(self, EP_CLIENT, "Proxy error encountered, error file not found.\r\n", 49);
    }
  else
    {
      GString *new_contents = g_string_sized_new(4096);
      gchar contents[4096], *src;
      gint count;
      
      count = read(fd, contents, sizeof(contents) - 1);
      close(fd);
      if (count == -1)
	count = 0;
      contents[count] = 0;
      src = contents;
      while (*src)
        {
          if (*src == '@')
            {
              if (strncmp(src, "@INFO@", 6) == 0)
                {
                  /* add info */
                  src += 5;
		  g_string_append_len(new_contents, infomsg->str, infomsg->len);
                }
              else if (strncmp(src, "@VERSION@", 9) == 0)
                {
                  src += 8;
		  g_string_append(new_contents, VERSION);
                }
              else if (strncmp(src, "@DATE@", 6) == 0)
		{
		  time_t t;
		  gchar timebuf[64];
		  struct tm tm;

		  src += 5;
		  t = time(NULL);
		  localtime_r(&t, &tm);
		  strftime(timebuf, sizeof(timebuf), "%c", &tm);
		  g_string_append(new_contents, timebuf);
		}
	      else
		g_string_append_c(new_contents, *src);
	    }
	  else
	    g_string_append_c(new_contents, *src);
            
	  src++;
	}
      http_write(self, EP_CLIENT, new_contents->str, new_contents->len);
      g_string_free(new_contents, TRUE);
    }
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_client_stream_init(HttpProxy *self)
{
  z_proxy_enter(self);
  self->readline[EP_CLIENT] = z_read_line_new(self->super.endpoints[EP_CLIENT], self->max_line_length, ZRL_EOL_CRLF);
  /* timeout is initialized after the config event */
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_server_stream_init(HttpProxy *self)
{
  z_proxy_enter(self);
  self->readline[EP_SERVER] = z_read_line_new(self->super.endpoints[EP_SERVER], self->max_line_length, ZRL_EOL_CRLF);
  self->super.endpoints[EP_SERVER]->timeout = self->timeout;
  z_proxy_leave(self);
  return TRUE;
}

gboolean
http_read(HttpProxy *self, int side, gchar *buf, size_t buflen, guint *bytes_read)
{
  guint res;
  
  z_proxy_enter(self);
  /* try to fetch data already buffered in readline */
  if (z_read_line_has_unread_data(self->readline[side]))
    {
      res = z_read_line_get_unread_data(self->readline[side], buf, buflen, bytes_read);
      if (ST_ERROR(res) != ST_OK)
        {
          z_proxy_log(self, HTTP_ERROR, 1, "Error fetching already buffered data, res=%x", res);
          z_proxy_leave(self);
          return FALSE;
        }
      z_proxy_leave(self);
      return TRUE;
    }
  /* no data in readline, fetch from the stream */
  res = z_stream_read(self->super.endpoints[side], buf, buflen, bytes_read);
  if (ST_ERROR(res) != ST_OK)
    {
      z_proxy_log(self, HTTP_ERROR, 1, "Error reading %s stream, res=%x, error=%m", side == EP_CLIENT ? "client" : "server", res);
      z_proxy_leave(self);
      return FALSE;
    }
  z_proxy_leave(self);
  return TRUE;
}

gboolean
http_write(HttpProxy *self, int side, gchar *buf, size_t buflen)
{
  guint res, bytes_written;
  
  z_proxy_enter(self);
  if (!self->super.endpoints[side])
    {
      /* write to NULL stream */
      z_proxy_log(self, HTTP_ERROR, 1, "Error writing %s stream, stream is NULL", side == EP_CLIENT ? "client" : "server");
      z_proxy_leave(self);
      return FALSE;
    }
  res = z_stream_write(self->super.endpoints[side], buf, buflen, &bytes_written);
  if (ST_ERROR(res) != ST_OK || buflen != bytes_written)
    {
      /* FIXME: move this to a separate function */
      z_proxy_log(self, HTTP_ERROR, 1, "Error writing %s stream, res=%x, error=%m", side == EP_CLIENT ? "client" : "server", res);

      z_stream_shutdown(self->super.endpoints[side], SHUT_RDWR);
      z_stream_close(self->super.endpoints[side]);
      z_stream_unref(self->super.endpoints[side]);
      z_read_line_destroy(self->readline[side]);
      self->super.endpoints[side] = NULL;
      self->readline[side] = NULL;
      
      self->error_code = HTTP_MSG_IO_ERROR;
      self->error_status = 502;
      g_string_sprintf(self->error_info, "Error writing to %s (%m)", side == EP_CLIENT ? "client" : "server");
      z_proxy_leave(self);
      return FALSE;
    }

  z_proxy_leave(self);
  return TRUE;
}   

static gboolean
http_hash_get_type(ZPolicyObj *tuple, guint *filter_type)
{
  ZPolicyObj *tmp;

  z_enter();
  if (!z_policy_seq_check(tuple))
    {
      if (z_policy_var_parse(tuple, "i", filter_type))
        {
          z_leave();
          return TRUE;
        }
      /* not a sequence nor an int */
      z_leave();
      return FALSE;
    }
    
  tmp = z_policy_seq_getitem(tuple, 0);
  if (!z_policy_var_parse(tmp, "i", filter_type))
    {
      /* policy syntax error */
      z_policy_var_unref(tmp);
      z_leave();
      return FALSE;
    }
  z_policy_var_unref(tmp);
  z_leave();
  return TRUE;
}


#define SKIP_SPACES \
  do \
    { \
      while (left > 0 && *src == ' ') \
        { \
          src++; \
          left--; \
        } \
    } \
  while (0)
  
#define COPY_SPACE \
  do \
    { \
      while (left > 0 && avail > 0 && *src != ' ' && *src) \
        { \
          *dst++ = *src++; \
          left--; \
          avail--; \
        } \
      *dst = 0; \
    } \
  while (0)
	

static gboolean
http_split_request(HttpProxy *self, gchar *line, guint length)
{
  gchar *src, *dst;
  gint left, avail;

  z_proxy_enter(self);
  self->request_method[0] = 0;
  self->request_flags = -1;
  self->request_version[0] = 0;

  g_string_truncate(self->request_url, 0);

  src = line;
  left = length;
  dst = self->request_method;
  avail = sizeof(self->request_method) - 1;

  COPY_SPACE;

  if (!self->request_method[0] || (*src != ' ' && avail == 0))
    {
      z_proxy_log(self, HTTP_REQUEST, 3, "Request method empty, or too long; line='%.*s'", length, src);
      z_proxy_leave(self);
      /* request method empty, or request buffer overflow */
      return FALSE;
    }

  SKIP_SPACES;

  avail = self->max_url_length;
  g_string_truncate(self->request_url, 0);

  while (left > 0 && avail > 0 && *src != ' ' && *src) 
    { 
      g_string_append_c(self->request_url, *src++);
      left--; 
      avail--; 
    } 

  if (!self->request_url->str[0] || (*src != ' ' && avail == 0))
    {
      /* url missing, or too long */
      z_proxy_log(self, HTTP_REQUEST, 3, "URL missing, or too long; line='%.*s'", length, src);
      z_proxy_leave(self);
      return FALSE;
    }

  SKIP_SPACES;

  dst = self->request_version;
  avail = sizeof(self->request_version) - 1;
  
  COPY_SPACE;

  if (*src != ' ' && avail == 0)
    {
      /* protocol version too long */
      z_proxy_log(self, HTTP_REQUEST, 3, "Protocol version, or too long; line='%.*s'", length, src);
      z_proxy_leave(self);
      return FALSE;
    }

  z_proxy_log(self, HTTP_REQUEST, 6, "Request: command=%s, url=%s, version=%s", self->request_method, self->request_url->str, self->request_version);
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_split_response(HttpProxy *self, gchar *line, guint line_length, gboolean *reply09)
{
  gchar *src, *dst;
  gint left, avail;

  z_proxy_enter(self);
  self->response_version[0] = 0;
  self->response[0] = 0;
  self->response_msg[0] = 0;
  *reply09 = FALSE;
  
  src = line;
  left = line_length;
  
  dst = self->response_version;
  avail = sizeof(self->response_version) - 1;

  COPY_SPACE;
  
  if (memcmp(self->response_version, "HTTP", 4) != 0)
    {
      /* no status line */
      *reply09 = TRUE;
      z_proxy_leave(self);
      return FALSE;
    }

  if (!self->response_version[0] || (*src != ' ' && avail == 0))
    {
      /* response version empty or too long */
      z_proxy_log(self, HTTP_REQUEST, 3, "response version empty or too long, line=%.*s", line_length, line);
      z_proxy_leave(self);
      return FALSE;
    }
  
  SKIP_SPACES;
  
  dst = self->response;
  avail = sizeof(self->response) - 1;

  COPY_SPACE;
  
  if (!self->response[0] || (*src != ' ' && left && avail == 0))
    {
      /* response code empty or too long */
      z_proxy_log(self, HTTP_REQUEST, 3, "response code empty or too long, line=%.*s", line_length, line);
      z_proxy_leave(self);
      return FALSE;
    }
  
  self->response_code = atoi(self->response);
    
  SKIP_SPACES;

  dst = self->response_msg;
  avail = sizeof(self->response_msg) - 1;

  while (left > 0 && avail > 0) 
    { 
      *dst++ = *src++; 
      left--; 
      avail--; 
    } 
  *dst = 0; 
  
  z_proxy_log(self, HTTP_REQUEST, 7, "Response; version='%s', response_code='%s', response_msg='%s'", self->response_version, self->response, self->response_msg);
  z_proxy_leave(self);
  return TRUE;
}

#undef SKIP_SPACES
#undef COPY_SPACE

static gboolean
http_parse_version(HttpProxy *self, gchar *version)
{
  z_proxy_enter(self);
  if (strcasecmp(version, "HTTP/1.1") == 0)
    {
      self->proto_version = 0x0101;
    }
  else if (strcasecmp(version, "HTTP/1.0") == 0)
    {
      self->proto_version = 0x0100;
    }
  else if (version[0] == 0)
    {
      self->proto_version = 0x0009;
    }
  else
    {
      /* unknown protocol version */
      z_proxy_log(self, HTTP_REQUEST, 3, "Unknown protocol version; version='%s'", version);
      self->proto_version = 0x0100;
      z_proxy_leave(self);
      return FALSE;
    }
  z_proxy_leave(self);
  return TRUE;
}

static void
http_log_headers(HttpProxy *self, gchar *tag, guint side)
{
  gchar *dir = (side == EP_CLIENT ? "request" : "response");
  gchar *src = self->headers[side]->str, *eol;

  if (z_log_enabled(HTTP_REQUEST, 7))
    {
      while (*src)
        {
          eol = strchr(src, '\n');
          if (eol && (eol - src - 1 > 0))
    	    {
    	      z_proxy_log(self, HTTP_REQUEST, 7, "%s %s header; hdr='%.*s'", tag, dir, eol - src - 1, src);
            }
          src = eol + 1;
        }
    }
}

static void
http_insert_headers(gchar *key, ZPolicyObj *f, GString *headers)
{
  guint filter_type;
  gchar *value;

  if (!http_hash_get_type(f, &filter_type))
    {
      /* filter has no type field */
      return;
    }
  switch (filter_type)
    {
    case HTTP_HDR_INSERT:
    case HTTP_HDR_REPLACE:
      if (!z_policy_var_parse(f, "(is)", &filter_type, &value))
	{
	  /* error parsing HTTP_INSERT or HTTP_REPLACE rule */
	  return;
	}
      g_string_append(headers, key);
      g_string_append(headers, ": ");
      g_string_append(headers, value);
      g_string_append(headers, "\r\n");
      break;
    default:
      break;
    }
}

static gboolean
http_filter_headers(HttpProxy *self, guint side, HttpHeaderFilter filter)
{
  GString *headers = self->headers[side];
  GString *new_headers = g_string_sized_new(headers->len);
  GHashTable *hash = (side == EP_CLIENT) ? self->request_header_policy : self->response_header_policy;
  gchar *src = headers->str, *eol, *colon;
  gint action;

  z_proxy_enter(self);  
  while (*src)
    {
      eol = strchr(src, '\n');
      colon = strchr(src, ':');
      if (eol && colon && (colon < eol))
	{
	  gchar *name, *value;
	  guint name_len, value_len;
	  gboolean dont_append = FALSE;
	  ZPolicyObj *f;

	  eol--;
	  if (*eol != '\r')
	    {
	      /* header not terminated by CRLF */
	      g_string_free(new_headers, 1);
	      z_proxy_leave(self);
	      return FALSE;
	    }
	  eol--;

	  name = src;
	  name_len = colon - src;

	  colon++;
	  while (*colon == ' ') colon++;

	  value = colon;
	  value_len = eol - colon + 1;
	  src = eol + 3;
	  
	  g_string_truncate(self->current_header_name, 0);
	  g_string_append_len(self->current_header_name, name, name_len);
	  g_string_truncate(self->current_header_value, 0);
	  g_string_append_len(self->current_header_value, value, value_len);

	  if (filter)
	    action = filter(self, self->current_header_name, self->current_header_value);
	  else
	    action = -1;

	  f = g_hash_table_lookup(hash, self->current_header_name->str);
	  if (!f)
	    f = g_hash_table_lookup(hash, "*");
	  if (f)
	    {
	      guint filter_type;
	      ZPolicyObj *handler, *res;

	      z_policy_lock(self->super.thread);
	      if (!http_hash_get_type(f, &filter_type))
		{
		  /* filter has no type field */
		  z_policy_unlock(self->super.thread);
		  z_proxy_leave(self);
		  return FALSE;
		}
	      z_policy_unlock(self->super.thread);
	      switch (filter_type)
		{
		case HTTP_HDR_POLICY:
		  z_policy_lock(self->super.thread);
		  if (!z_policy_var_parse(f, "(iO)", &filter_type, &handler))
		    {
		      /* error parsing HTTP_POLICY_CALL rule */
		      z_proxy_leave(self);
		      return FALSE;
		    }
		  res = z_policy_call_object(handler, 
					     z_policy_var_build("(s#s#)", 
								name, name_len, 
								value, value_len));
		  if (!z_policy_var_parse(res, "i", &action))
		    {
		      z_proxy_log(self, HTTP_POLICY, 1, "policy callback for header [%s] returned non-int value", self->current_header_name->str);
		      z_policy_var_unref(res);
		      z_policy_unlock(self->super.thread);
		      z_proxy_leave(self);
		      return FALSE;
		    }
		  z_policy_var_unref(res);
		  z_policy_unlock(self->super.thread);
		  break;
		case HTTP_HDR_ACCEPT:
		case HTTP_HDR_INSERT:
		  break;
		case HTTP_HDR_REPLACE:
		case HTTP_HDR_DROP:
		  dont_append = TRUE;
		  break;
		case HTTP_HDR_CHANGE_NAME:
		  z_policy_lock(self->super.thread);
		  if (!z_policy_var_parse(f, "(is)", &filter_type, &name))
		    {
		      /* invalid CHANGE_NAME rule */
		      z_proxy_log(self, HTTP_POLICY, 1, "Invalid HTTP_HDR_CHANGE_NAME rule in header processing;");
		      z_policy_unlock(self->super.thread);
		      z_proxy_leave(self);
		      return FALSE;
		    }
		  g_string_assign(self->current_header_name, name);
		  z_policy_unlock(self->super.thread);
		  break;
		case HTTP_HDR_CHANGE_VALUE:
		  z_policy_lock(self->super.thread);
		  if (!z_policy_var_parse(f, "(is)", &filter_type, &value))
		    {
		      /* invalid CHANGE_VALUE rule */
		      z_proxy_log(self, HTTP_POLICY, 1, "Invalid HTTP_HDR_CHANGE_VALUE rule in header processing;");
		      z_policy_unlock(self->super.thread);
		      z_proxy_leave(self);
		      return FALSE;
		    }
		  g_string_assign(self->current_header_value, value);
		  z_policy_unlock(self->super.thread);
		  break;
		case HTTP_HDR_CHANGE_BOTH:
		  z_policy_lock(self->super.thread);
		  if (!z_policy_var_parse(f, "(iss)", &filter_type, &name, &value))
		    {
		      /* invalid CHANGE_BOTH rule */
		      z_proxy_log(self, HTTP_POLICY, 1, "Invalid HTTP_HDR_CHANGE_BOTH rule in header processing;");
		      z_policy_unlock(self->super.thread);
		      z_proxy_leave(self);
		      return FALSE;
		    }
		  g_string_assign(self->current_header_name, name);
		  g_string_assign(self->current_header_value, value);
		  z_policy_unlock(self->super.thread);
		  break;
		}
	    }
	  switch (action)
	    {
	    case HTTP_HDR_ACCEPT:
	      break;
	    case HTTP_HDR_DROP:
	      dont_append = 1;
	      break;
	    default:
	      self->error_code = HTTP_MSG_POLICY_VIOLATION;
	      z_proxy_leave(self);
	      return FALSE;
	    }
	  if (!dont_append)
	    {
	      g_string_append(new_headers, self->current_header_name->str);
	      g_string_append(new_headers, ": ");
	      g_string_append(new_headers, self->current_header_value->str);
	      g_string_append(new_headers, "\r\n");
	    }
	}
      else
	{
	  if (*(eol - 1) == '\r' && (src == eol - 1))
	    {
	      /* empty line */
	      break;
	    }
	  else
	    {
	      g_string_free(new_headers, 1);
	      z_proxy_leave(self);
	      return FALSE;
	    }
	}
    }
  g_string_assign(headers, new_headers->str);

  z_policy_lock(self->super.thread);
  g_hash_table_foreach(hash, (GHFunc) http_insert_headers, headers);
  z_policy_unlock(self->super.thread);

  g_string_append(headers, "\r\n");
  g_string_free(new_headers, 1);
  z_proxy_leave(self);
  z_proxy_leave(self);
  return TRUE;
}

static gboolean 
http_data_transfer(HttpProxy *self, guint from, guint to, gboolean expect_data)
{
  GString *headers = self->headers[from];
  gint content_length;
  HttpTransferFilter filter_transfer;
  gchar *hdr;
  guint hdr_len;

  z_proxy_enter(self);
  filter_transfer = http_transfer_filter_copy;

  content_length = -2;  // -1 unknown, -2 no data

  if (http_header_lookup(headers, "Transfer-Encoding", &hdr, &hdr_len, TRUE) &&
      strncasecmp(hdr, "chunked", MIN(hdr_len, 7)) == 0)
    {
      filter_transfer = http_transfer_filter_chunked;
      content_length = -1;
    }
  else if (http_header_lookup(headers, "Content-Length", &hdr, &hdr_len, TRUE))
    {
      char buf[hdr_len + 1];
      gchar *end;
      
      memcpy(buf, hdr, hdr_len);
      buf[hdr_len] = 0;
      content_length = strtoul(buf, &end, 10);
      if (end - buf != hdr_len)
	{
	  /* content-length not a number */
	  z_proxy_leave(self);
	  return FALSE;
	}
      if (content_length == 0)
	content_length = -2;
      
      filter_transfer = http_transfer_filter_copy;
    }
  else if (expect_data)
    {
      content_length = -1;
      self->connection_mode = HTTP_CONNECTION_CLOSE; 
    }
  else 
    {
      content_length = -2;
    }

  if (content_length != -2)
    {
      if (!filter_transfer(self, from, to, content_length))
	{
	  /* FIXME: transfer unsuccessful */
	  z_proxy_log(self, HTTP_ERROR, 2, "Data transfer failed;");
	  self->error_code = HTTP_MSG_OK; /* error message may corrupt already buffered data, close connection silently */
	  z_proxy_leave(self);
	  return FALSE;
	}
    }
  z_proxy_leave(self);
  return TRUE;
}

/* FIXME: resolve continuation lines */
static gboolean
http_fetch_headers(HttpProxy *self, int side)
{
  GString *headers = self->headers[side];
  gchar *line;
  guint res, line_length;
  guint count = 0;

  z_proxy_enter(self);
  /* check if we have to fetch request headers */
  if (self->proto_version < 0x0100)
    {
      z_proxy_leave(self);
      return TRUE;
    }
  
  g_string_assign(headers, "");
  while (1)
    {
      gchar *colon;
      
      res = z_read_line_get(self->readline[side], &line, &line_length);
      if (ST_ERROR(res) != ST_OK)
        {
          if (side == EP_SERVER && self->permit_null_response)
            break;
          z_proxy_log(self, HTTP_ERROR, 3, "Error reading from peer while fetching headers;");
          z_proxy_leave(self);
	  return FALSE;
	}
      if (line_length == 0)
        {
          break;
        }
      for (colon = line; colon - line < line_length && *colon != ' ' && *colon != ':'; colon++)
        ;
      if (*colon != ':')
        {
          z_proxy_log(self, HTTP_ERROR, 3, "Invalid HTTP header; line='%.*s'", line_length, line);
	  if (self->strict_header_checking)
	    {
              z_proxy_leave(self);
	      return FALSE;
	    }
	  else
	    continue;
        }
      /* strip trailing white space */
      while (line_length > 0 && line[line_length - 1] == ' ') 
        line_length--;
      g_string_append_len(headers, line, line_length);
      
      g_string_append(headers, "\r\n");
      count++;
      if (count > self->max_header_lines)
        {
          /* too many headers */
          z_proxy_log(self, HTTP_ERROR, 3, "Too many header lines; max_header_lines='%d'", self->max_header_lines);
          z_proxy_leave(self);
          return FALSE;
        }
    }
  g_string_append(headers, "\r\n");
  
  http_log_headers(self, "prefilter", side);
  
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_fetch_request(HttpProxy *self)
{
  gchar *line;
  guint line_length;
  guint res;

  z_proxy_enter(self); 
  while (1) 
    {
      self->super.endpoints[EP_CLIENT]->timeout = self->timeout_request;
      res = z_read_line_get(self->readline[EP_CLIENT], &line, &line_length);
      self->super.endpoints[EP_CLIENT]->timeout = self->timeout;
      if (ST_ERROR(res) == ST_EOF)
        {
          self->error_code = HTTP_MSG_OK;
          z_proxy_leave(self);
          return FALSE;
        }
      else if (ST_ERROR(res) != ST_OK)
        {
          self->error_code = HTTP_MSG_OK; 
          if (errno == ETIME)
            {
              if (self->request_count == 0)
                {
	          self->error_code = HTTP_MSG_CLIENT_TIMEOUT;
                  self->error_status = 408;
                }
            }
          z_proxy_leave(self);
	  return FALSE;
	}
      if (line_length != 0)
	break;
    }
  if (!http_split_request(self, line, line_length))
    {
      z_proxy_leave(self);
      return FALSE;
    }
  self->request_flags = http_proto_request_lookup(self->request_method);
  if (self->request_flags == -1)
    {
      z_proxy_leave(self);
      return FALSE;
    }
    
  if (!http_parse_version(self, self->request_version))
    {
      z_proxy_leave(self);
      return FALSE;
    }

  if (!http_fetch_headers(self, EP_CLIENT))
    {
      z_proxy_leave(self);
      return FALSE;
    }

  z_proxy_leave(self);
  return TRUE;
}

static gint
http_translate_base64_char(guchar c)
{
  static char base64_alphabet[128] = 
    {
     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /*  0*/
     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 16*/
     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,  /* 32*/
     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,  /* 48*/
     -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 64*/
     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,  /* 80*/
     -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,  /* 96*/
     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1   /*112*/
    };
  if (c == '=')
    return -2;
  else if (c > 127)
    return -1;
  else
    return base64_alphabet[c];
}

static gboolean
http_process_base64_quantum(guchar *dst, gint *dstlen, guchar *src, gint srclen)
{
  gchar c[4];
  int checked = 0;
  int i, j;
  
  if (srclen < 4)
    {
      return FALSE;
    }
  c[0] = http_translate_base64_char(src[0]);
  c[1] = http_translate_base64_char(src[1]);
  c[2] = http_translate_base64_char(src[2]);
  c[3] = http_translate_base64_char(src[3]);

  if (*dstlen < 3)
    return FALSE;
    
  *dstlen = 3;
  
  /* sanity check quantum */
  for (i = 0; i < 4; i++)
    if (c[i] == -2)
      {
        c[i] = 0;
        if (!checked)
          {
            if (i == 2)
              *dstlen = 1;
            else if (i == 3)
              *dstlen = 2;
            else
              return FALSE;
              
            checked = 1;
            for (j = i + 1; j < 4; j++)
              if (c[j] != -2)
                return FALSE;
          }
      }
  *dst = (c[0] << 2) | ((c[1] & 0x30) >> 4); 
  dst++;
  *dst = ((c[1] & 0x0f) << 4) | ((c[2] & 0x3e) >> 2);
  dst++;
  *dst = ((c[2] & 0x03) << 6) | (c[3] & 0x3f);
  dst++;
  return TRUE;
}

static gboolean
http_process_base64(gchar *dst, gint dstlen, gchar *src, gint srclen)
{
  while (srclen > 0 && dstlen > 0)
    {
      gint l;
      
      l = dstlen;
      
      if (!http_process_base64_quantum(dst, &l, src, srclen))
        return FALSE;
        
      dstlen -= l;
      srclen -= 4;
      src += 4;
      dst += l;
    }
  if (srclen != 0)
    return FALSE;
  *dst = 0;
  return TRUE;
}

/* FIXME: optimize header processing a bit (no need to copy hdr into a
   buffer) */
static gboolean
http_process_auth_info(HttpProxy *self, gchar *hdr, gint hdr_length)
{
  gchar buf[2048];
  gchar userpass[128];
  gchar *p;
  gchar **up;

  z_proxy_enter(self);  
  hdr_length = MIN(hdr_length, sizeof(buf) - 1);
  strncpy(buf, hdr, hdr_length);
  buf[hdr_length] = 0;
  
  if (self->old_auth_header->len &&
      strcmp(buf, self->old_auth_header->str) == 0)
    {
      z_proxy_leave(self);
      return TRUE;
    }
  
  g_strstrip(buf);
  if (strncmp(buf, "Basic", 5) != 0)
    {
      z_proxy_log(self, HTTP_REQUEST, 3, "Only Basic authentication is supported;");
      /* not basic auth */
      z_proxy_leave(self);
      return FALSE;
    }
    
  p = buf + 5;
  while (*p == ' ') 
    {
      p++;
    }
  if (!http_process_base64(userpass, sizeof(userpass), p, strlen(p)))
    {
      z_proxy_log(self, HTTP_REQUEST, 3, "Invalid base64 encoded username:password pair;");
      z_proxy_leave(self);
      return FALSE;
    }
  
  up = g_strsplit(userpass, ":", 2);
  if (up)
    {
      gboolean res = FALSE;
      gchar challenge_buffer[1];
      gchar **avail_methods;
      gchar *method;
      
      z_policy_lock(self->super.thread);
      avail_methods = z_authorization_get_methods(self->auth, self->super.session_id, up[0]);
      if (avail_methods)
        {
          method = avail_methods[0];
      
          z_authorization_get_challenge(self->auth, challenge_buffer, sizeof(challenge_buffer), self->super.session_id, up[0], method);
          if (challenge_buffer[0])
            {
              res = FALSE;
            }
          else
            {
              res = z_authorization_check_credentials(self->auth, self->super.session_id, up[0], method, challenge_buffer, up[1]);
              if (res)
                {
                  /* this shouldn't be needed, just for safety */
                  g_string_truncate(self->old_auth_header, 0);
                  g_string_append_len(self->old_auth_header, hdr, hdr_length);
                }
            }
          g_strfreev(avail_methods);
        }
      
      z_policy_unlock(self->super.thread);
      z_proxy_user_authenticated(&self->super, up[0]);
      g_strfreev(up);
      z_proxy_leave(self);
      return res;
    }
  z_proxy_leave(self);
  return FALSE;
}

static gboolean
http_process_request(HttpProxy *self)
{
  gchar *hdr;
  guint hdr_length;

  z_proxy_enter(self);  
  if (self->proto_version > 0x0100)
    self->connection_mode = HTTP_CONNECTION_KEEPALIVE;
  else    
    self->connection_mode = HTTP_CONNECTION_CLOSE;
    
  if (self->request_flags & HTTP_REQ_FLG_CONNECT)
    {
      if (self->proto_version >= 0x0100)
        {
          self->request_type = HTTP_REQTYPE_PROXY;
          z_proxy_leave(self);
          return TRUE;
        }
      else
        {
          self->error_code = HTTP_MSG_CLIENT_SYNTAX;
          g_string_sprintf(self->error_info, "CONNECT method requires HTTP/1.0 or later");
          z_proxy_log(self, HTTP_REQUEST, 3, "CONNECT method without version specification;");
          z_proxy_leave(self);
          return FALSE;
        }
    }
    
  if (self->proto_version <= 0x0100)
    {
      /* no proxy protocol for version 0.9 */
      self->request_type = HTTP_REQTYPE_SERVER;
    }
  else if (http_header_lookup(self->headers[EP_CLIENT], "Proxy-Connection", &hdr, &hdr_length, 1))
    {
      if (http_header_lookup(self->headers[EP_CLIENT], "Connection", &hdr, &hdr_length, 1))
	{
	  /* both Proxy-Connection & Connection headers ... */
	  self->error_code = HTTP_MSG_CLIENT_SYNTAX;
	  g_string_sprintf(self->error_info, "Both Proxy-Connection and Connection headers exist.");
	  z_proxy_log(self, HTTP_REQUEST, 3, "Both Proxy-Connection and Connection headers exist;");
	  z_proxy_leave(self);
	  return FALSE;
	}
      self->request_type = HTTP_REQTYPE_PROXY;
    }
  else if (http_header_lookup(self->headers[EP_CLIENT], "Connection", &hdr, &hdr_length, 1))
    {
      self->request_type = HTTP_REQTYPE_SERVER;
    }
  else if (self->request_url->str[0] != '/')
    {
      /* neither Connection nor Proxy-Connection header exists, and URI
         doesn't seem to be a simple filename */
      self->request_type = HTTP_REQTYPE_PROXY;
    }
  else
    {
      /* default */
      self->request_type = HTTP_REQTYPE_SERVER;
    }

  if (self->auth)
    {
      /* authentication is required */
      if (self->transparent_mode)
        {
          self->error_code = HTTP_MSG_POLICY_SYNTAX;
          g_string_sprintf(self->error_info, "Authentication is not available in transparent mode.");
          z_proxy_log(self, HTTP_REQUEST, 3, "Authentication is not available in transparent mode;");
          z_proxy_leave(self);
          return FALSE;
        }
      if (!http_header_lookup(self->headers[EP_CLIENT], "Proxy-Authorization", &hdr, &hdr_length, 1) ||
          !http_process_auth_info(self, hdr, hdr_length))
        {
          self->error_code = HTTP_MSG_AUTH_REQUIRED;
          self->error_status = 407;
          g_string_sprintf(self->error_info, "Authentication is required.");
          g_string_sprintfa(self->error_headers, "Proxy-Authenticate: Basic realm=\"%s\"\r\n", self->auth_realm->str);
          z_proxy_leave(self);
          return FALSE;
        }
    }

  if (self->transparent_mode &&
      ((self->request_type == HTTP_REQTYPE_PROXY && !self->permit_proxy_requests) ||
       (self->request_type == HTTP_REQTYPE_SERVER && !self->permit_server_requests)))
    {
      /* */
      self->error_code = HTTP_MSG_POLICY_VIOLATION;
      g_string_sprintf(self->error_info, "%s requests not permitted in transparent mode.", 
		       self->request_type == HTTP_REQTYPE_SERVER ? "server" : "proxy");
      z_proxy_log(self, HTTP_REQUEST, 3, "%s", self->error_info->str);
      z_proxy_leave(self);
      return FALSE;
    }

  if (((self->request_type == HTTP_REQTYPE_SERVER) && 
       http_header_lookup(self->headers[EP_CLIENT], "Connection", &hdr, &hdr_length, 1)) ||
      ((self->request_type == HTTP_REQTYPE_PROXY) && 
       http_header_lookup(self->headers[EP_CLIENT], "Proxy-Connection", &hdr, &hdr_length, 1)))
    {
      if (strncasecmp(hdr, "Keep-Alive", hdr_length) == 0)
        {
          self->connection_mode = HTTP_CONNECTION_KEEPALIVE;
        }
      else if (strncasecmp(hdr, "Close", hdr_length) == 0)
        {
          self->connection_mode = HTTP_CONNECTION_CLOSE;
        }
    }

  if (!http_check_url(self->request_url->str))
    {
      /* invalid url */
      self->error_code = HTTP_MSG_INVALID_URL;
      g_string_sprintf(self->error_info, "Invalid url %s", self->request_url->str);
      z_proxy_log(self, HTTP_REQUEST, 3, "Invalid URL; url='%s'", self->request_url->str);
      z_proxy_leave(self);
      return FALSE;
    }

  if (http_header_lookup(self->headers[EP_CLIENT], "Host", &hdr, &hdr_length, 1))
    {
      int len = MIN(hdr_length, sizeof(self->remote_server) - 1);
      
      memcpy(self->remote_server, hdr, len);
      self->remote_server[len] = 0;
    }


  if (self->transparent_mode)
    {
      if (self->request_url->str[0] == '/')
	{
	  gchar buf[strlen(self->remote_server) + 32];
	  
	  /* no protocol description */
	  if (!self->remote_server[0])
	    {
	      if (!self->require_host_header)
	        {
	          /* no host header */
	          z_proxy_log(self, HTTP_REQUEST, 6, "No host header in transparent request.");
	          strcpy(self->remote_server, "unknown");
	        }
	      else
	        {
                  self->error_code = HTTP_MSG_CLIENT_SYNTAX;
                  if (self->proto_version < 0x0100)
                    {
                      g_string_sprintf(self->error_info, "'Host:' header is required, and HTTP/0.9 can't transfer headers.");
                    }
                  else
                    {
                      g_string_sprintf(self->error_info, "No 'Host:' header in request, and policy requires this.");
                    }
                  z_proxy_log(self, HTTP_REQUEST, 3, "%s", self->error_info->str);
	          z_proxy_leave(self);
	          return FALSE;
	        }
	    }
	  g_snprintf(buf, sizeof(buf), "http://%s", self->remote_server);
	  g_string_prepend(self->request_url, buf);
	}
    }
  z_proxy_leave(self);
  return TRUE;  
} 

static guint 
http_request_filter_connection_header(HttpProxy *self, GString *name, GString *value)
{
  z_proxy_enter(self);
  if (self->rewrite_host_header &&
      strcasecmp(name->str, "Host") == 0)
    {
      /* NOTE: the constant 80 below is intentional, if
       * default_port is changed we still have to send
       * correct host header (containing port number)
       */
      if (self->remote_port != 80 && self->remote_port != 0)
	g_string_sprintf(value, "%s:%d", self->remote_server, self->remote_port);
      else
	g_string_assign(value, self->remote_server);
      
      z_proxy_leave(self);
      return HTTP_HDR_ACCEPT;
    }
  switch (self->request_type)
    {
    case HTTP_REQTYPE_SERVER:
      /* if we have a parent proxy, Connection -> Proxy-Connection
       * otherwise leave it as is 
       */
      if (self->parent_proxy->len &&
	  strcasecmp(name->str, "Connection") == 0)
	g_string_assign(name, "Proxy-Connection");
      break;
    case HTTP_REQTYPE_PROXY:
      /* if we have a parent proxy leave it as is
       * otherwise Proxy-Connection -> Connection 
       */
      if (self->parent_proxy->len == 0 &&
	  strcasecmp(name->str, "Proxy-Connection") == 0)
	g_string_assign(name, "Connection");
      break;
    }
  z_proxy_leave(self);
  return HTTP_HDR_ACCEPT;
}

static gboolean
http_filter_request(HttpProxy *self)
{
  ZPolicyObj *f;
  gint rc;

  z_proxy_enter(self);
  f = g_hash_table_lookup(self->request_method_policy, self->request_method);
  if (!f)
    f = g_hash_table_lookup(self->request_method_policy, "*");
  if (f)
    {
      ZPolicyObj *handler, *res;
      gchar *errmsg;
      gint filter_type;
      
      z_policy_lock(self->super.thread);  
      if (!http_hash_get_type(f, &filter_type))
        {
	  z_proxy_log(self, HTTP_POLICY, 1, "invalid request hash item(%s)", self->request_method);
	  z_policy_unlock(self->super.thread);
	  z_proxy_leave(self);
	  return FALSE;
          
        }
      z_policy_unlock(self->super.thread);
      g_string_sprintf(self->error_info, "Method %s denied by policy", self->request_method);

      switch (filter_type)
        {
        case HTTP_REQ_POLICY:
	  z_policy_lock(self->super.thread);
          if (!z_policy_var_parse(f, "(iO)", &filter_type, &handler))
            {
              z_proxy_log(self, HTTP_POLICY, 1, "error parsing HTTP_REQ_POLICY rule");
              z_policy_unlock(self->super.thread);
              z_proxy_leave(self);
              return FALSE;
            }
          res = z_policy_call_object(handler, z_policy_var_build("(sss)", self->request_method, self->request_url->str, self->request_version));
          if (!res || !z_policy_var_parse(res, "i", &rc))
            {
	      rc = HTTP_REQ_REJECT;
              g_string_assign(self->error_info, "Error in policy handler.");
            }
          z_policy_var_unref(res);
	  z_policy_unlock(self->super.thread);
          break;
	case HTTP_REQ_REJECT:
	  errmsg = NULL;
          z_policy_lock(self->super.thread);
	  if (!z_policy_var_parse_tuple(f, "i|s", &filter_type, &errmsg))
	    {
	      z_proxy_log(self, HTTP_POLICY, 1, "error parsing HTTP_REQ_REJECT");
              z_policy_unlock(self->super.thread);
	      z_proxy_leave(self);
              return FALSE;
	    }
          if (errmsg)
	    g_string_assign(self->error_info, errmsg);
          /* fallthrough */
	case HTTP_REQ_ACCEPT:
	case HTTP_REQ_DENY:
	case HTTP_REQ_ABORT:
	  /* dropped command */
	  rc = filter_type;
	  break;
	default:
	  z_proxy_log(self, HTTP_POLICY, 1, "invalid request hash item, request=%s", self->request_method);
	  z_proxy_leave(self);
	  return FALSE;
        }
      switch (rc)
        {
        case HTTP_REQ_ACCEPT:
          break;
        default:
	  self->error_code = HTTP_MSG_POLICY_VIOLATION;
	  z_proxy_leave(self);
          return FALSE;
        }
      z_proxy_leave(self);
      return TRUE;
    }
  self->error_code = HTTP_MSG_POLICY_VIOLATION;
  g_string_sprintf(self->error_info, "Method %s denied by policy", self->request_method);
  z_proxy_leave(self);
  return FALSE;
}

static gboolean
http_connect_server(HttpProxy *self)
{
  z_proxy_enter(self);
  if (!self->super.endpoints[EP_SERVER] ||
      strcasecmp(self->remote_server, self->connected_server) != 0 ||
      self->remote_port != self->connected_port)
    {
      gboolean success = FALSE;
      
      if (self->super.endpoints[EP_SERVER])
	{
	  z_read_line_destroy(self->readline[EP_SERVER]);
	  z_stream_shutdown(self->super.endpoints[EP_SERVER], SHUT_RDWR);
	  z_stream_close(self->super.endpoints[EP_SERVER]);
	  self->readline[EP_SERVER] = NULL;
	  self->super.endpoints[EP_SERVER] = NULL;
	}
      
      g_string_sprintf(self->error_info, "Error establishing connection to %s", self->remote_server);
      if (self->transparent_mode)
        success = z_proxy_connect_server_event(&self->super, NULL, 0);
      else
        {
          if (z_port_enabled(self->target_port_range->str, self->remote_port))
            {
              success = z_proxy_connect_server_event(&self->super, self->remote_server, self->remote_port);
            }
          else
            {
              g_string_sprintf(self->error_info, "Connecting to port %d is prohibited by policy.", self->remote_port);
              success = FALSE;
            }
        }

      if (!success)
	{
	  /* error connecting to server */
	  self->error_code = HTTP_MSG_CONNECT_ERROR;
	  self->error_status = 502;
	  z_proxy_leave(self);
	  return FALSE;
	}

      strcpy(self->connected_server, self->remote_server);
      self->connected_port = self->remote_port;
      if (!http_server_stream_init(self))
        {
          /* should never happen */
          z_proxy_leave(self);
          return FALSE;
        }
    }
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_copy_request(HttpProxy *self)
{
  gchar *request;
  gchar *proto, *user, *passwd, *host, *file;
  guint proto_len, user_len, passwd_len, host_len, file_len;
  int attempts = 0;
  
  z_proxy_enter(self);
  if (!http_split_url(self->request_url->str, 
		      &proto, &proto_len, 
		      &user, &user_len,
		      &passwd, &passwd_len, 
		      &host, &host_len,
		      &self->remote_port,
		      &file, &file_len))
    {
      /* invalid URL */
      self->error_code = HTTP_MSG_INVALID_URL;
      g_string_sprintf(self->error_info, "Badly formatted url: %s", self->request_url->str);
      z_proxy_leave(self);
      return FALSE;
    }
  if (self->remote_port == 0)
    self->remote_port = self->default_port;
  
  g_string_assign_len(self->url_proto, proto, proto_len);
  g_string_assign_len(self->url_username, user, user_len);
  g_string_assign_len(self->url_passwd, passwd, passwd_len);
  g_string_assign_len(self->url_host, host, host_len);
  if (file_len)
    g_string_assign_len(self->url_file, file, file_len);
  else
    g_string_assign(self->url_file, "/");
  self->url_port = self->remote_port;
  
  if (host_len > self->max_hostname_length)
    {
      self->error_code = HTTP_MSG_CLIENT_SYNTAX;
      g_string_sprintf(self->error_info, "Too long hostname in URL: %.*s", host_len, host);
      z_proxy_leave(self);
      return FALSE;
    }

  if (strncasecmp(proto, "http://", 7) != 0 &&
      strncasecmp(proto, "ftp://", 6) != 0 &&
      strncasecmp(proto, "cache_object://", 9) != 0)
    {
      /* unsupported protocol */
      self->error_code = HTTP_MSG_CLIENT_SYNTAX;
      g_string_sprintf(self->error_info, "Unsupported protocol: %.*s", (int) proto_len, proto);
      z_proxy_leave(self);
      return FALSE;
    }

  if (self->parent_proxy->len)
    {
      host_len = MIN(self->parent_proxy->len, sizeof(self->remote_server) - 1);
      memcpy(self->remote_server, self->parent_proxy->str, host_len);
      self->remote_port = self->parent_proxy_port;
    }
  else
    {
      host_len = MIN(host_len, sizeof(self->remote_server) - 1);
      memcpy(self->remote_server, host, host_len);
    }

  self->remote_server[host_len] = 0;

  if (self->proto_version >= 0x0100)
    {
      if (!http_filter_headers(self, EP_CLIENT, http_request_filter_connection_header))
	{
	  z_proxy_leave(self);
	  return FALSE;
	}
      http_log_headers(self, "postfilter", EP_CLIENT);
      /* z_proxy_log(self, HTTP_REQUEST, 7, "postfilter request=%s", self->headers[EP_CLIENT]->str); */
    }
  

  while (attempts < 4)
    {
      attempts++;
      if (!http_connect_server(self))
        {
          z_proxy_leave(self);
	  return FALSE;
	}
      if (self->proto_version >= 0x0100)
	{
	  request = alloca(strlen(self->request_method) + self->request_url->len + strlen(self->request_version) + self->headers[EP_CLIENT]->len + 8);
	  if (self->parent_proxy->len)
	    sprintf(request, "%s %s %s\r\n%s", self->request_method, self->request_url->str, self->request_version, self->headers[EP_CLIENT]->str);
	  else
	    sprintf(request, "%s %s %s\r\n%s", self->request_method, self->url_file->str, self->request_version, self->headers[EP_CLIENT]->str);
	}
      else
	{
	  request = alloca(strlen(self->request_method) + self->request_url->len + 5);
	  sprintf(request, "%s %s\r\n", self->request_method, self->request_url->str);
	}
  
      if (!http_write(self, EP_SERVER, request, strlen(request)))
        {
	  if (attempts < 4)
	    continue;

 	  self->error_code = HTTP_MSG_CONNECT_ERROR;
 	  self->error_status = 502;
 	  g_string_sprintf(self->error_info, "Error establishing connection to %s", self->remote_server);
 	  
	  z_proxy_leave(self);
	  return FALSE;
	}
    
      if (http_data_transfer(self, EP_CLIENT, EP_SERVER, FALSE))
	break;
      else 
        {
          z_proxy_leave(self);
          return FALSE;
        }

    }

  z_proxy_leave(self);
  return TRUE;
}

static gboolean 
http_fetch_response(HttpProxy *self)
{
  gchar *line;
  guint line_length;
  guint res;
  gboolean reply09;

  z_proxy_enter(self);
  self->response[0] = 0;
  self->response_code = -1;
  if (self->proto_version < 0x0100)
    {
      z_proxy_leave(self);
      return TRUE;
    }
  
  while (self->response_code == -1 || self->response_code == 100)
    {
      res = z_read_line_get(self->readline[EP_SERVER], &line, &line_length);
      if (ST_ERROR(res) != ST_OK)
        {
          self->error_code = HTTP_MSG_OK;
          z_proxy_leave(self);
	  return FALSE;
	}
      
      if (!http_split_response(self, line, line_length, &reply09))
        {
          if (reply09)
            {
              if (self->request_count)
                {
                  /* this is a kept-alive request -> HTTP/0.9 is not possible */
                  z_proxy_leave(self);
                  return FALSE;
                }
              z_read_line_unget(self->readline[EP_SERVER], line, line_length);
	      self->proto_version = 0x0009;
	      z_proxy_leave(self);
	      return TRUE;
            }
          z_proxy_leave(self);
	  return FALSE;
	}
      
      self->response_flags = http_proto_response_lookup(self->response);
      if (self->response_flags == -1)
        {
          z_proxy_leave(self);
	  return FALSE;
	}
      if (!http_parse_version(self, self->response_version))
        {
          z_proxy_leave(self);
	  return FALSE;
	}
      
      if (!http_fetch_headers(self, EP_SERVER))
        {
          z_proxy_leave(self);
	  return FALSE;
	}
    }

  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_process_response(HttpProxy *self)
{
  gchar *hdr;
  guint hdr_length;

  z_proxy_enter(self);
  /* previously set by http_parse_version */
  switch (self->proto_version)
    {
    case 0x0009:
      strncpy(self->response, "200", sizeof(self->response_code));
      self->response_code = 200;
      self->response_flags = HTTP_RESP_FLG_TRANSFER | HTTP_RESP_FLG_DATA_COND;
      self->connection_mode = HTTP_CONNECTION_CLOSE;
      z_proxy_leave(self);
      return TRUE;
    case 0x0100:
      self->connection_mode = HTTP_CONNECTION_CLOSE;
      break;
    case 0x0101:
      /* leave connection mode as is, as was requested by
	 the browser */
      break;
    default:
      z_proxy_log(self, HTTP_ERROR, 1, "Unsupported protocol version 0x%04x", self->proto_version);
      z_proxy_leave(self);
      return FALSE;
    }

  if ((self->request_type == HTTP_REQTYPE_SERVER && 
       http_header_lookup(self->headers[EP_SERVER], "Connection", &hdr, &hdr_length, 1)) ||
      (self->request_type == HTTP_REQTYPE_PROXY && 
       http_header_lookup(self->headers[EP_SERVER], "Proxy-Connection", &hdr, &hdr_length, 1)))
    {
      if (strncasecmp(hdr, "Keep-Alive", hdr_length) == 0)
        {
          self->connection_mode = HTTP_CONNECTION_KEEPALIVE;
        }
      else 
        {
          self->connection_mode = HTTP_CONNECTION_CLOSE;
        }
    }

  if (self->response_flags & HTTP_RESP_FLG_STOP)
    {
      z_proxy_leave(self);
      return FALSE;  /* FIXME: not implemented */
    }

  z_proxy_leave(self);
  return TRUE;
}

static guint
http_response_filter_connection_header(HttpProxy *self, GString *name, GString *value)
{
  z_proxy_enter(self);
  switch (self->request_type)
    {
    case HTTP_REQTYPE_SERVER:
      /* if we have a parent proxy, Proxy-Connection -> Connection
       * otherwise leave it as is 
       */
      if (self->parent_proxy->len &&
	  strcasecmp(name->str, "Proxy-Connection") == 0)
	g_string_assign(name, "Connection");
      if (self->connection_mode == HTTP_CONNECTION_CLOSE &&
	  strcasecmp(name->str, "Connection") == 0)
	{
	  g_string_assign(value, "close");
	}
      break;
    case HTTP_REQTYPE_PROXY:
      /* if we have a parent proxy leave it as is
       * otherwise Connection -> Proxy-Connection 
       */
      if (self->parent_proxy->len == 0 &&
	  strcasecmp(name->str, "Connection") == 0)
	g_string_assign(name, "Proxy-Connection");

      if (self->connection_mode == HTTP_CONNECTION_CLOSE &&
	  strcasecmp(name->str, "Proxy-Connection") == 0)
	{
	  g_string_assign(value, "close");
	}
      break;
    }
  z_proxy_leave(self);
  return HTTP_HDR_ACCEPT;
}

static ZPolicyObj *
http_response_policy_get(HttpProxy *self)
{
  gchar *response_keys[2];

  response_keys[0] = self->request_method;
  response_keys[1] = self->response;
  
  return z_dim_hash_table_search(self->response_policy, 2, response_keys);
}

static gboolean
http_filter_response(HttpProxy *self)
{
  z_proxy_enter(self);
  if (self->proto_version >= 0x0100)
    {
      ZPolicyObj *f, *res;
      gint rc;
      
      f = http_response_policy_get(self);
      if (f)
        {
          ZPolicyObj *handler;
	  gint filter_type;
          gchar *errmsg;
      
          z_policy_lock(self->super.thread);  
          if (!http_hash_get_type(f, &filter_type))
            {
	      z_proxy_log(self, HTTP_POLICY, 1, "invalid response hash item(%s, %d)", self->request_method, self->response_code);
	      z_policy_unlock(self->super.thread);
	      z_proxy_leave(self);
	      return FALSE;
          
            }
          z_policy_unlock(self->super.thread);

          switch (filter_type)
            {
            case HTTP_RSP_POLICY:
	      z_policy_lock(self->super.thread);
              if (!z_policy_var_parse(f, "(iO)", &filter_type, &handler))
                {
                  z_proxy_log(self, HTTP_POLICY, 1, "error parsing HTTP_POLICY_CALL rule");
                  z_policy_unlock(self->super.thread);
                  z_proxy_leave(self);
                  return FALSE;
                }
              res = z_policy_call_object(handler, z_policy_var_build("(sssi)", self->request_method, self->request_url->str, self->request_version, self->response_code));
              if (!z_policy_var_parse(res, "i", &rc))
                {
		  g_string_assign(self->error_info, "Error in policy handler.");
                  rc = HTTP_RSP_REJECT;
                }
              z_policy_var_unref(res);
	      z_policy_unlock(self->super.thread);
	      break;
	    case HTTP_RSP_REJECT:
	      errmsg = NULL;
	      z_policy_lock(self->super.thread);
	      if (!z_policy_var_parse(f, "(i|s)", &filter_type, &errmsg))
		{
		  z_proxy_log(self, HTTP_POLICY, 1, "error parsing HTTP_REQ_REJECT");
		  z_policy_unlock(self->super.thread);
		  z_proxy_leave(self);
		  return FALSE;
		}
	      if (errmsg)
		g_string_assign(self->error_info, errmsg);
              /* fallthrough */
	    case HTTP_RSP_ACCEPT:
	    case HTTP_RSP_DENY:
	    case HTTP_RSP_ABORT:
	      /* dropped command */
	      rc = filter_type;
	      break;
	    default:
	      z_proxy_log(self, HTTP_POLICY, 1, "invalid response hash item, request=%s, response=%d", self->request_method, self->response_code);
	      z_proxy_leave(self);
	      return FALSE;
            }
          switch (rc)
            {
            case HTTP_RSP_ACCEPT:
              break;
            default:
	      g_string_sprintf(self->error_info, "Response %d for %s denied by policy", self->response_code, self->request_method);
	    case HTTP_RSP_REJECT:
	      self->error_code = HTTP_MSG_POLICY_VIOLATION;
	      z_proxy_leave(self);
              return FALSE;
            }

        }
      
      if (!http_filter_headers(self, EP_SERVER, http_response_filter_connection_header))
        {
          z_proxy_leave(self);
          return FALSE;
        }
      http_log_headers(self, "postfilter", EP_SERVER);
      /* z_proxy_log(self, HTTP_RESPONSE, 7, "postfilter response=%s", self->headers[EP_SERVER]->str); */
    }
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_copy_response(HttpProxy *self)
{
  z_proxy_enter(self);
  if (self->proto_version >= 0x0100)
    {
      gchar *response; 
      
      response = alloca(strlen(self->response_version) + strlen(self->response) + strlen(self->response_msg) + self->headers[EP_SERVER]->len + 6);
      sprintf(response, "%s %s %s\r\n%s", self->response_version, self->response, self->response_msg, self->headers[EP_SERVER]->str);
      if (!http_write(self, EP_CLIENT, response, strlen(response)))
        {
          z_proxy_leave(self);
          return FALSE;
        }
    }

  if ((((self->request_flags & HTTP_REQ_FLG_HEAD) != 0) && 
       ((self->response_flags & HTTP_RESP_FLG_DATA_COND) != 0)) ||       //head & data_cond OR
      (((self->response_flags & HTTP_RESP_FLG_DATA) == 0) && 
       ((self->response_flags & HTTP_RESP_FLG_DATA_COND) == 0)))          //!data & !data_cond
    {
      /* no need to transfer data */
      
    }
  else
    {
      if (!http_data_transfer(self, EP_SERVER, EP_CLIENT, TRUE))
        {
          z_proxy_leave(self);
	  return FALSE;
	}
    }
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
http_handle_connect(HttpProxy *self)
{
  /* connect is enabled here */
  int i, rc; 
  ZPolicyObj *res;
  gchar *colon, *end, *success;
  gboolean called;

  z_proxy_enter(self);
  if (self->parent_proxy->len)
    {
      z_proxy_log(self, HTTP_POLICY, 1, "CONNECT method is not supported with parent proxies");
      z_proxy_leave(self);
      return FALSE;
    }

  colon = strchr(self->request_url->str, ':');
  if (colon)
    {
      *colon = 0;
      strncpy(self->remote_server, self->request_url->str, sizeof(self->remote_server) - 1);
      self->remote_server[sizeof(self->remote_server)] = 0;
      *colon = ':';
      self->remote_port = strtoul(colon + 1, &end, 10);
    }
  else
    {
      self->error_code = HTTP_MSG_CLIENT_SYNTAX;
      g_string_sprintf(self->error_info, "Invalid CONNECT request.");
      z_proxy_leave(self);
      return FALSE;
    }
          
  if (!http_connect_server(self))
    {
      z_proxy_leave(self);
      return FALSE;
    }
  
  z_proxy_log(self, HTTP_DEBUG, 6, "starting connect method");
  
  z_policy_lock(self->super.thread);
  res = z_policy_call(self->super.handler, "connectMethod", z_policy_var_build("()"), &called);
  
  if (!z_policy_var_parse(res, "i", &rc))
    {
      self->error_code = HTTP_MSG_POLICY_SYNTAX;
      z_policy_var_unref(res);
      z_policy_unlock(self->super.thread);
      z_proxy_leave(self);
      return FALSE;
    }
  z_policy_var_unref(res);
  z_policy_unlock(self->super.thread);
  
  if (rc != HTTP_REQ_ACCEPT)
    {
      g_string_assign(self->error_info, "CONNECT request denied by policy.");
      z_proxy_leave(self);
      return FALSE;
    }
  
  success = "HTTP/1.0 200 Connection established\r\n\r\n";
  if (!http_write(self, EP_CLIENT, success, strlen(success)))
    {
      /* hmm... I/O error */
      z_proxy_leave(self);
      return FALSE;
    }
  
  /* release, but don't close fds */
  for (i = EP_CLIENT; i < EP_MAX; i++)
    {
      z_read_line_destroy(self->readline[i]);
      self->readline[i] = NULL;
      z_stream_unref(self->super.endpoints[i]);
      self->super.endpoints[i] = NULL;
    }
  z_proxy_leave(self);
  return TRUE;
}

static void
http_main(HttpProxy *self)
{
  self->request_count = 0;
  
  z_proxy_enter(self);
  /* loop for keep-alive */
  while (1)
    {
      self->error_code = -1;
      z_proxy_log(self, HTTP_DEBUG, 6, "fetching request and headers");
      /* fetch request */
      if (!http_fetch_request(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_CLIENT_SYNTAX;
	  break;
	}
      z_proxy_log(self, HTTP_DEBUG, 6, "processing request and headers");
      if (!http_process_request(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_CLIENT_SYNTAX;
	  break;
	}
      z_proxy_log(self, HTTP_DEBUG, 6, "filtering request and headers");
      if (!http_filter_request(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_POLICY_SYNTAX;
	  break;
	}
      
      if ((self->request_flags & HTTP_REQ_FLG_CONNECT))
        {
          if (http_handle_connect(self))
            {
              /* connect method was successful, we can now safely quit */
              z_proxy_leave(self);
              return;
            }
          else
            break;
        }
      if (self->max_keepalive_requests != 0 && 
          self->request_count > self->max_keepalive_requests)
        {
          z_proxy_log(self, HTTP_DEBUG, 6, "max_keepalive_request reached, setting connection mode to close");
          self->connection_mode = HTTP_CONNECTION_CLOSE;
        }
      z_proxy_log(self, HTTP_DEBUG, 6, "sending request and headers, copying request data");
      if (!http_copy_request(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_CLIENT_SYNTAX;
	  break;
	}
      z_proxy_log(self, HTTP_DEBUG, 6, "fetching response and headers");
      if (!http_fetch_response(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_SERVER_SYNTAX;
	  break;
	}
      z_proxy_log(self, HTTP_DEBUG, 6, "processing response and headers");
      if (!http_process_response(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_SERVER_SYNTAX;
          break;
        }
      z_proxy_log(self, HTTP_DEBUG, 6, "filtering response and headers");
      if (!http_filter_response(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_POLICY_SYNTAX;
          break;
        }
      z_proxy_log(self, HTTP_DEBUG, 6, "copying response and headers, copying response data");
      if (!http_copy_response(self))
        {
          if (self->error_code == -1)
            self->error_code = HTTP_MSG_SERVER_SYNTAX;
          break;
        }
      if (self->connection_mode == HTTP_CONNECTION_CLOSE ||
	  self->response_flags & HTTP_RESP_FLG_CLOSE)
	break;
	
      self->request_count++;
    }
  z_proxy_log(self, HTTP_DEBUG, 6, "exiting keep-alive loop");
  if (self->error_code != -1)
    {
      http_error_message(self, self->error_status, self->error_code, self->error_info);
    }
  z_proxy_leave(self);
}

static void
http_thread(void *s)
{
  HttpProxy *self = (HttpProxy *) s;

  z_proxy_enter(self);
  http_config_set_defaults(self);
  http_register_vars(self);

  ZPROXY_SET_STATE(self, PS_CONFIG);
  if (z_proxy_config_event(&self->super))
    {
  
      http_config_init(self);
      http_client_stream_init(self);

      ZPROXY_SET_STATE(self, PS_STARTING_UP);
      if (z_proxy_startup_event(&self->super))
        {

          ZPROXY_SET_STATE(self, PS_WORKING);

          http_main(self);

          ZPROXY_SET_STATE(self, PS_SHUTTING_DOWN);
          z_proxy_shutdown_event(&self->super);
        }
    }
  z_proxy_destroy(&self->super);
  z_leave();
}

static void
http_proxy_free(ZProxy *s)
{
  HttpProxy *self = (HttpProxy *) s;
  int i;

  z_proxy_enter(s);
  for (i = EP_CLIENT; i < EP_MAX; i++)
    {
      if (self->readline[i])
        {
          z_read_line_destroy(self->readline[i]);
          self->readline[i] = NULL;
        }
      g_string_free(self->headers[i], TRUE);
    }

#if 0
  g_string_free(self->current_header_name, TRUE);
  g_string_free(self->current_header_value, TRUE);
  g_string_free(self->request_url, TRUE);

  g_string_free(self->url_proto, TRUE);
  g_string_free(self->url_username, TRUE);
  g_string_free(self->url_passwd, TRUE);
  g_string_free(self->url_host, TRUE);
  g_string_free(self->url_file, TRUE);

  g_string_free(self->parent_proxy, TRUE);
  g_string_free(self->error_info, TRUE);
  g_string_free(self->error_headers, TRUE);
  g_string_free(self->auth_realm, TRUE);
  g_string_free(self->old_auth_header, TRUE);
  
  g_string_free(self->target_port_range, TRUE);
#endif
  /* NOTE: hashes are freed up by pyvars */
  
  z_proxy_leave(s);
}

static ZProxy *
http_proxy_new(gchar *session_id, ZStream *client, ZPolicyObj *handler)
{
  HttpProxy *self = g_new0(HttpProxy, 1);

  z_enter();
  z_proxy_init(&self->super, session_id, client, handler);
  self->super.free = http_proxy_free;
  
  z_thread_new(session_id, http_thread, self);
  
  z_leave();
  return (&self->super);
}

gint
zorp_module_init(void)
{
  z_registry_add("http", ZR_SPROXY, http_proxy_new);
  return TRUE;
}
