/*
 * Copyright (c) 1999-2001 Caucho Technology.  All rights reserved.
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  The following
 * conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 *
 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL CAUCHO OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 *
 * @author Scott Ferguson
 */

#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"
#include <stdlib.h>
#include <errno.h>

#if !defined(EAPI) && (MODULE_MAGIC_COOKIE == MODULE_MAGIC_COOKIE_EAPI)
#define EAPI 1
#endif

#include "cse.h"
#include "version.h"

/*
 * Apache magic module declaration.
 */
module MODULE_VAR_EXPORT caucho_module;

#define DEFAULT_PORT 6802

static char *g_error_page = 0;
static config_t *g_config = 0;

void
cse_log(char *fmt, ...)
{
#ifdef DEBUG
  va_list args;
  FILE *file = fopen("/tmp/log", "a+");

  if (file) {  
    time_t now = time(0);
    struct tm *now_tm = localtime(&now);
    char date[80];
    
    strftime(date, sizeof(date), "%H:%M:%S", now_tm);
    fprintf(file, "%d:[%s] ", getpid(), date);

    va_start(args, fmt);
    vfprintf(file, fmt, args);
    va_end(args);
    fclose(file);
  }
#endif
}

void *
cse_create_lock(config_t *config)
{
#ifdef WIN32
	return CreateMutex(0, 0, 0);
#else
    return 0;
#endif
}

int
cse_lock(void *lock)
{
#ifdef WIN32
  if (lock) {
    WaitForSingleObject(lock, INFINITE);
  }
  return 1;
#else
  return 1;
#endif
}

void
cse_unlock(void *lock)
{
#ifdef WIN32
	if (lock)
		ReleaseMutex(lock);
#endif
}

void
cse_error(config_t *config, char *format, ...)
{
  char buf[BUF_LENGTH];
  va_list args;

  va_start(args, format);
  vsprintf(buf, format, args);
  va_end(args);

  LOG(("%s\n", buf));

  config->error = cse_strdup(config->p, buf);
  config->disable_caucho_status = 0;
}

void
cse_set_socket_cleanup(int socket, void *pool)
{
  LOG(("set cleanup %d\n", socket));

  if (socket > 0)
    ap_note_cleanups_for_socket(pool, socket);
}

void
cse_kill_socket_cleanup(int socket, void *pool)
{
  LOG(("kill cleanup %d\n", socket));

  if (socket > 0)
    ap_kill_cleanups_for_socket(pool, socket);
}

void *
cse_malloc(int size)
{
  return malloc(size);
}

void cse_free(void *data) {}

static void
cse_module_init(struct server_rec *server, struct pool *pool)
{
  ap_add_version_component(VERSION);
}

static void *
cse_create_server_config(pool *p, server_rec *server)
{
  config_t *config = (config_t *) malloc(sizeof(config_t));
  srun_item_t *srun_list = (srun_item_t *) malloc(16 * sizeof(srun_item_t));

  memset(config, 0, sizeof(config_t));
  memset(srun_list, 0, 16 * sizeof(srun_item_t));

  config->p = cse_create_pool(config);
  config->srun_list = srun_list;
  config->srun_capacity = 16;
  config->srun_size = 0;
  config->session_url_prefix = ";jsessionid=";
  config->round_robin_index = -1;

  g_config = config;
  
  return (void *) config;
}

static void *
cse_create_dir_config(pool *p, char *dir)
{
  config_t *config = (config_t *) malloc(sizeof(config_t));
  srun_item_t *srun_list = (srun_item_t *) malloc(16 * sizeof(srun_item_t));

  memset(config, 0, sizeof(config_t));
  memset(srun_list, 0, 16 * sizeof(srun_item_t));

  config->p = cse_create_pool(config);
  config->srun_list = srun_list;
  config->srun_capacity = 16;
  config->srun_size = 0;
  config->session_url_prefix = ";jsessionid=";
  config->path = "no-assigned-path";
  
  if (! g_config)
    g_config = config;
  
  return (void *) config;
}

/**
 * Retrieves the caucho configuration from Apache
 */
static config_t *
cse_get_module_config(request_rec *r)
{
  config_t *config = 0;

  if (r->per_dir_config)
    config = (config_t *) ap_get_module_config(r->per_dir_config, &caucho_module);
  if (config)
    return config;

  if (r->server->module_config)
    config = (config_t *) ap_get_module_config(r->server->module_config,
                                               &caucho_module);

  if (config)
    return config;
  else
    return g_config;
}

static config_t *
cse_get_cmd_config(cmd_parms *cmd)
{
  config_t *config = (config_t *) ap_get_module_config(cmd->context, &caucho_module);

  if (config)
    return config;
  
  config = (config_t *) ap_get_module_config(cmd->server->module_config,
                                             &caucho_module);
  if (config)
    return config;
  else
    return g_config;
}

/**
 * Parse the CauchoConfigHost configuration in the apache config file.
 */
static const char *
cse_config_file_command(cmd_parms *cmd, void *mconfig, char *value)
{
  config_t *config = cse_get_cmd_config(cmd);
 
  config->path = ap_server_root_relative(cmd->pool, value);

  cse_init_config(config);

  return 0;
}

/**
 * Parse the CauchoHosts configuration in the apache config file.
 */
static const char *
cse_host_command(cmd_parms *cmd, void *mconfig, char *host_arg, char *port_arg)
{
  config_t *config = cse_get_cmd_config(cmd);
  int port = port_arg ? atoi(port_arg) : DEFAULT_PORT;

  cse_add_host(config, host_arg, port);

  return 0;
}

/**
 * Parse the CauchoBackup configuration in the apache config file.
 */
static const char *
cse_backup_command(cmd_parms *cmd, void *mconfig,
		   char *host_arg, char *port_arg)
{
  config_t *config = cse_get_cmd_config(cmd);
  int port = port_arg ? atoi(port_arg) : DEFAULT_PORT;

  cse_add_backup(config, host_arg, port);

  return 0;
}

/**
 * Parse the CauchoKeepalive configuration in the apache config file.
 */
static const char *
cse_keepalive_command(cmd_parms *cmd, void *mconfig, int flag)
{
  /* g_srun_keepalive = flag; */

  return 0;
}

/**
 * Parse the CauchoHosts configuration in the apache config file.
 */
static const char *
cse_error_page_command(cmd_parms *cmd, void *mconfig, char *error_page_arg)
{
  g_error_page = error_page_arg;

  return 0;
}

/**
 * Look at the request to see if Caucho should handle it.
 */
static int
cse_dispatch(request_rec *r)
{
  config_t *config = cse_get_module_config(r);
  const char *host = ap_get_server_name(r);
  const char *uri = r->uri;
  char *url_rewrite;
  char *new_uri;

  if (config == NULL)
    return DECLINED;

  cse_update_config(config, r->request_time);
  
  if (! config->disable_caucho_status && strstr(uri, "/caucho-status")) {
    r->handler = "caucho-status";
    return OK;
  }

  /* Check for exact virtual host match */
  if (cse_match_request(config, host, ap_get_server_port(r), uri, 0)) {
    r->handler = "caucho-request";
    
    LOG(("[%d] match %s:%s\n", getpid(), host ? host : "null", uri));
    return OK;
  }

  LOG(("[%d] mismatch %s:%s\n", getpid(), host ? host : "null", uri));
  
  new_uri = strstr(uri, config->session_url_prefix);
  if (new_uri) {
    if (r->request_config) {
      ap_set_module_config(r->request_config, &caucho_module, "session");
    }
    
    *new_uri = 0;
  
    /* Strip session encoding from static files. */
    if (r->filename) {
      url_rewrite = strstr(r->filename, config->session_url_prefix);
    
      if (url_rewrite) {
        *url_rewrite = 0;

        if (stat(r->filename, &r->finfo) < 0) {
          LOG(("killing mode %s\n", uri));
          r->finfo.st_mode = 0;
        }
      }
    }
  }

  return DECLINED;
}

/**
 * Gets the session index from the request
 *
 * Cookies have priority over the query
 *
 * @return -1 if no session
 */
static int
get_session_index(config_t *config, request_rec *r)
{
  array_header *hdrs_arr = ap_table_elts(r->headers_in);
  table_entry *hdrs = (table_entry *) hdrs_arr->elts;
  int i;
  int session;

  for (i = 0; i < hdrs_arr->nelts; ++i) {
    if (! hdrs[i].key || ! hdrs[i].val)
      continue;

    if (strcasecmp(hdrs[i].key, "Cookie"))
      continue;

    session = cse_session_from_string(hdrs[i].val, "JSESSIONID=");
    if (session >= 0)
      return session;
  }

  return cse_session_from_string(r->uri, config->session_url_prefix);
}

static int
connection_error(config_t *config, srun_t *srun, request_rec *r)
{
  char *error_page = config->error_page;
  
  if (! error_page && g_error_page)
    error_page = g_error_page;

  if (error_page && cse_match_request(config, "", 0, error_page, 0))
    error_page = 0;

  if (error_page) {
    /* need to use ap_internal_redirect to get the headers */
    ap_internal_redirect(error_page, r);
    return OK;
  }

  r->content_type = "text/html";
  if (error_page) {
    r->status = 302;
    ap_table_set(r->headers_out, "Location", error_page);
  }
  else
    r->status = 503;

  ap_send_http_header(r);

  ap_rputs("<html><body bgcolor='white'>", r);
  ap_rprintf(r, "<h1>Can't contact servlet runner at %s:%d</h1>", 
	     srun->hostname ? srun->hostname : "localhost",
	     srun->port);
  ap_rputs("</body></html>", r);
  
  ap_rputs("\n\n\n\n", r);
  ap_rputs("<!--\n", r);
  ap_rputs("  -- Unfortunately, Microsoft has added a clever new\n", r);
  ap_rputs("  -- \"feature\" to Internet Explorer.  If the text in\n", r);
  ap_rputs("  -- an error's message is \"too small\", specifically\n", r);
  ap_rputs("  -- less than 512 bytes, Internet Explorer returns\n", r);
  ap_rputs("  -- its own error message.  Yes, you can turn that\n", r);
  ap_rputs("  -- off, but *surprise* it's pretty tricky to find\n", r);
  ap_rputs("  -- buried as a switch called \"smart error\n", r);
  ap_rputs("  -- messages\"  That means, of course, that many of\n", r);
  ap_rputs("  -- Resin's error messages are censored by default.\n", r);
  ap_rputs("  -- And, of course, you'll be shocked to learn that\n", r);
  ap_rputs("  -- IIS always returns error messages that are long\n", r);
  ap_rputs("  -- enough to make Internet Explorer happy.  The\n", r);
  ap_rputs("  -- workaround is pretty simple: pad the error\n", r);
  ap_rputs("  -- message with a big comment to push it over the\n", r);
  ap_rputs("  -- five hundred and twelve byte minimum.  Of course,\n", r);
  ap_rputs("  -- that's exactly what you're reading right now.\n", r);
  ap_rputs("  -->\n", r);

  ap_kill_timeout(r);

  return OK;
}

/**
 * Writes request parameters to srun.
 */
static void
write_env(stream_t *s, request_rec *r)
{
  char buf[4096];
  int ch;
  int i;
  
  conn_rec *c = r->connection;
  const char *host;
  int port;

  cse_write_string(s, CSE_PROTOCOL, r->protocol);
  cse_write_string(s, CSE_METHOD, r->method);
  for (i = 0; (ch = r->uri[i]) && ch != '?' && i + 1 < sizeof(buf); i++) 
    buf[i] = ch;
  buf[i] = 0;
  cse_write_string(s, CSE_URI, buf);

  if (r->args)
    cse_write_string(s, CSE_QUERY_STRING, r->args);

  /* Gets the server name */
  host = ap_get_server_name(r);
  port = ap_get_server_port(r);

  cse_write_string(s, CSE_SERVER_NAME, host);
  cse_write_string(s, CSE_SERVER_PORT, ap_psprintf(r->pool, "%u", port));

  host = ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST);
  if (host)
    cse_write_string(s, CSE_REMOTE_HOST, host);
  else
    cse_write_string(s, CSE_REMOTE_HOST, c->remote_ip);

  cse_write_string(s, CSE_REMOTE_ADDR, c->remote_ip);
  cse_write_string(s, CSE_REMOTE_PORT,
		   ap_psprintf(r->pool, "%d", ntohs(c->remote_addr.sin_port)));
  
  if (c->user)
    cse_write_string(s, CSE_REMOTE_USER, c->user);
  if (c->ap_auth_type)
    cse_write_string(s, CSE_AUTH_TYPE, c->ap_auth_type);

  cse_write_string(s, CSE_SESSION_GROUP,
		   ap_psprintf(r->pool, "%d", s->srun->session));
}

static void
write_ssl_env(stream_t *s, request_rec *r)
{
  /* mod_ssl */
#ifdef EAPI
  {
    static char *vars[] = { "SSL_CLIENT_S_DN",
                            "SSL_CIPHER",
                            "SSL_CIPHER_EXPORT",
                            "SSL_PROTOCOL",
                            "SSL_CIPHER_USEKEYSIZE",
                            "SSL_CIPHER_ALGKEYSIZE",
                            0};
    char *var;
    int i;
    int v;
    
    if ((v = ap_hook_call("ap::mod_ssl::var_lookup", &var, r->pool, r->server,
                          r->connection, r, "SSL_CLIENT_CERT"))) {
      cse_write_string(s, CSE_CLIENT_CERT, var);
    }
    else if ((v = ap_hook_call("ap::mod_ssl::var_lookup", &var, r->pool, r->server,
                          r->connection, r, "SSL_CLIENT_CERTIFICATE"))) {
      cse_write_string(s, CSE_CLIENT_CERT, var);
    }

    for (i = 0; vars[i]; i++) {
      if ((v = ap_hook_call("ap::mod_ssl::var_lookup", &var,
                            r->pool, r->server, r->connection, r, vars[i]))) {
        cse_write_string(s, CSE_HEADER, vars[i]);
        cse_write_string(s, CSE_VALUE, var);
      }
    }
  }
#endif  
}

/**
 * Writes headers to srun.
 */
static void
write_headers(stream_t *s, request_rec *r)
{
  array_header *hdrs_arr = ap_table_elts(r->headers_in);
  table_entry *hdrs = (table_entry *) hdrs_arr->elts;
  int i;

  for (i = 0; i < hdrs_arr->nelts; ++i) {
    char *key = hdrs[i].key;
    char *value = hdrs[i].val;
    
    if (! key)
      continue;

    /*
     * Content-type and Content-Length are special cased for a little
     * added efficiency.
     */
    if (! strcasecmp(key, "Content-type"))
      cse_write_string(s, CSE_CONTENT_TYPE, value);
    else if (! strcasecmp(key, "Content-length"))
      cse_write_string(s, CSE_CONTENT_LENGTH, value);
    else {
      cse_write_string(s, CSE_HEADER, key);
      cse_write_string(s, CSE_VALUE, value);
    }
  }
}

static void
write_added_headers(stream_t *s, request_rec *r)
{
  array_header *hdrs_arr = ap_table_elts(r->subprocess_env);
  table_entry *hdrs = (table_entry *) hdrs_arr->elts;
  int i;
  int has_ssl = 0;

  for (i = 0; i < hdrs_arr->nelts; ++i) {
    char *key = hdrs[i].key;
    char *value = hdrs[i].val;
    
    if (! key)
      continue;

    /* skip leading whitespace */
    for (; isspace(*value); value++) {
    }

    if (! strcmp(key, "HTTPS") &&
	! strcasecmp(value, "on")) {
      has_ssl = 1;
      cse_write_string(s, CSE_IS_SECURE, "");
    }
    else if (*key == 'S' && ! r->connection->user &&
             ! strcmp(key, "SSL_CLIENT_DN"))
      cse_write_string(s, CSE_REMOTE_USER, value);

    cse_write_string(s, CSE_HEADER, key);
    cse_write_string(s, CSE_VALUE, value);
  }

  if (has_ssl)
    write_ssl_env(s, r);

  if (r->prev) {
    if (r->prev->args) {
      cse_write_string(s, CSE_HEADER, "REDIRECT_QUERY_STRING");
      cse_write_string(s, CSE_VALUE, r->prev->args);
    }
    
    if (r->prev->uri) {
      cse_write_string(s, CSE_HEADER, "REDIRECT_URL");
      cse_write_string(s, CSE_VALUE, r->prev->uri);
    }
  }
}

/**
 * Writes a response from srun to the client
 */
static int
cse_write_response(stream_t *s, int len, request_rec *r)
{
  while (len > 0) {
    int writelen;
    int sentlen;

    if (s->read_offset >= s->read_length && cse_fill_buffer(s) < 0)
      return -1;

    writelen = s->read_length - s->read_offset;
    if (len < writelen)
      writelen = len;

    while (writelen > 0) {
      sentlen = ap_rwrite(s->read_buf + s->read_offset, writelen, r);
      if (sentlen < 0) {
	cse_close(s, "write");
	return -1;
      }

      writelen -= sentlen;
      s->read_offset += sentlen;
      len -= sentlen;
    }
  }
  
  return 1;
}

/**
 * Copy data from the JVM to the browser.
 */
static int
send_data(stream_t *s, request_rec *r, int ack, int *keepalive)
{
  int code = CSE_END;
  char buf[8193];
  char key[8193];
  char value[8193];
  int i;

  ap_reset_timeout(r);
    
  if (cse_fill_buffer(s) < 0)
    return -1;
    
  do {
    int l1, l2, l3;
    int len;

    ap_reset_timeout(r);
    
    code = cse_read_byte(s);
    l1 = cse_read_byte(s) & 0xff;
    l2 = cse_read_byte(s) & 0xff;
    l3 = cse_read_byte(s);
    len = (l1 << 16) + (l2 << 8) + (l3 & 0xff);

    if (s->socket < 0)
      return -1;

    switch (code) {
    case CSE_STATUS:
      cse_read_limit(s, buf, sizeof(buf), len);
      for (i = 0; buf[i] && buf[i] != ' '; i++) {
      }
      i++;
      r->status = atoi(buf + i);
      r->status_line = ap_pstrdup(r->pool, buf + i);
      break;

    case CSE_HEADER:
      cse_read_limit(s, key, sizeof(key), len);
      cse_read_string(s, value, sizeof(value));
      if (! strcasecmp(key, "content-type"))
	r->content_type = ap_pstrdup(r->pool, value);
      else if (! strcasecmp(key, "set-cookie"))
	ap_table_add(r->headers_out, key, value);
      else
	ap_table_set(r->headers_out, key, value);
      break;

    case CSE_DATA:
      if (cse_write_response(s, len, r) < 0)
	return -1;
      break;

    case CSE_FLUSH:
      ap_rflush(r);
      break;

    case CSE_KEEPALIVE:
      *keepalive = 1;
      break;

    case CSE_SEND_HEADER:
      ap_send_http_header(r);
      break;

    case -1:
      break;

    case CSE_END:
    case CSE_CLOSE:
      cse_skip(s, len);
      break;
      
    default:
      cse_skip(s, len);
      break;
    }
  } while (code > 0 && code != CSE_END && code != CSE_CLOSE && code != ack);

  return code;
}

/**
 * handles a client request
 */
static int
write_request(stream_t *s, request_rec *r, config_t *config, int *keepalive,
              int session_index, char *ip)
{
  int len;
  int code;
  int write_length;

  write_env(s, r);
  write_headers(s, r);
  write_added_headers(s, r);

  /* read post data */
  if (ap_should_client_block(r)) {
    char buf[BUF_LENGTH];
    int isFirst = 1;

    while ((len = ap_get_client_block(r, buf, BUF_LENGTH)) > 0) {
      ap_reset_timeout(r);

      if (! isFirst) {
        code = send_data(s, r, CSE_ACK, keepalive);
        if (code < 0 || code == CSE_END || code == CSE_CLOSE)
          break;
      }

      cse_write_packet(s, CSE_DATA, buf, len);
      isFirst = 0;
    }
  }

  cse_write_packet(s, CSE_END, 0, 0);

  code = send_data(s, r, CSE_END, keepalive);

  if (code < 0 && ! s->sent_data) {
    write_length = s->write_length; 
    if (cse_open_connection(s, config, session_index, 
                            r->request_time, r->pool)) {
      s->write_length = write_length;
      LOG(("retry connection %d\n", s->socket));
      code = send_data(s, r, CSE_END, keepalive);
    }
    else {
      LOG(("retry fail\n"));
      connection_error(config, config->srun_list->srun, r);
  
      ap_kill_timeout(r);
      return 0;
    }
  }
  else if (code != CSE_END) {
    LOG(("can't retry %d %d.", code, s->sent_data));
  }

  return code == CSE_END;
}

#ifdef WIN32

int random() { return 0; }

#endif

/**
 * Handle a request.
 */
static int
caucho_request(request_rec *r)
{
  config_t *config = cse_get_module_config(r);
  stream_t s;
  int retval;
  int keepalive = 0;
  int reuse;
  int session_index;
  char *ip;
  unsigned int now = r->request_time;

  if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
    return retval;

  ap_soft_timeout("servlet request", r);
  
  if (r->request_config &&
      ap_get_module_config(r->request_config, &caucho_module)) {
    r->uri[strlen(r->uri)] = *config->session_url_prefix;
  }

  session_index = get_session_index(config, r);
  ip = r->connection->remote_ip;
 
  if (! cse_open_connection(&s, config, session_index, now, r->pool)) {
    connection_error(config, config->srun_list->srun, r);
  
    ap_kill_timeout(r);
    return OK;
  }

  reuse = write_request(&s, r, config, &keepalive, session_index, ip);
  
  ap_kill_timeout(r);
  ap_rflush(r);

  if (reuse)
    cse_recycle(&s, now);
  else
    cse_close(&s, "no reuse");

  return OK;
}

/**
 * Print the statistics for each JVM.
 */
static void
jvm_status(config_t *config, request_rec *r)
{
  int i;
  stream_t s;
  unsigned int now = r->request_time;

  ap_rputs("<center><table border=2 width='80%'>\n", r);
  ap_rputs("<tr><th>Host</th>\n", r);
  ap_rputs("    <th>Connect<br>Timeout</th>\n", r);
  ap_rputs("    <th>Live<br>Time</th>\n", r);
  ap_rputs("    <th>Dead<br>Time</th>\n", r);
  ap_rputs("</tr>\n", r);

  for (i = 0; i < config->srun_size; i++) {
    srun_item_t *srun_item = config->srun_list + i;
    srun_t *srun = srun_item->srun;
    int port;
    char group_buf[1024];

    if (! srun)
      continue;

    port = srun->port;
    ap_rputs("<tr>", r);

    if (srun_item->group && *srun_item->group)
      sprintf(group_buf, " (%s)", srun_item->group);
    else
      group_buf[0] = 0;

    if (cse_open(&s, config, srun_item, r->pool, 1) <= 0) {
      ap_rprintf(r, "<td bgcolor='#ff6666'>%d.%s %s:%d%s (down)</td>",
                 srun_item->session + 1, group_buf,
                 srun->hostname ? srun->hostname : "localhost",
                 port, srun_item->is_backup ? "*" : "");
    }
    else {
      ap_rprintf(r, "<td bgcolor='#66ff66'>%d.%s %s:%d%s (ok)</td>",
                 srun_item->session + 1, group_buf,
                 srun->hostname ? srun->hostname : "localhost",
                 port, srun_item->is_backup ? "*" : "");
    }
    
    cse_recycle(&s, now);
    
    ap_rprintf(r, "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>",
               srun->connect_timeout, srun->live_time, srun->dead_time);
    ap_rputs("</tr>\n", r);
  }
  ap_rputs("</table></center>\n", r);
}

static void
escape_html(char *dst, char *src)
{
  int ch;
  
  for (; (ch = *src); src++) {
    switch (ch) {
    case '<':
      *dst++ = '&';
      *dst++ = 'l';
      *dst++ = 't';
      *dst++ = ';';
      break;
      
    case '&':
      *dst++ = '&';
      *dst++ = 'a';
      *dst++ = 'm';
      *dst++ = 'p';
      *dst++ = ';';
      break;
      
    default:
      *dst++ = ch;
      break;
    }
  }

  *dst = 0;
}

/**
 * Print a summary of the configuration so users can understand what's
 * going on.  Ping the server to check that it's up.
 */
static int
caucho_status(request_rec *r)
{
  config_t *config = cse_get_module_config(r);
  web_app_t *app;
  location_t *loc;
 
  r->content_type = "text/html";
  ap_soft_timeout("caucho status", r);
  if (r->header_only) {
    ap_kill_timeout(r);

    return OK;
  }

  ap_send_http_header(r);

  ap_rputs("<html><title>Status : Caucho Servlet Engine</title>\n", r);
  ap_rputs("<body bgcolor=white>\n", r);
  ap_rputs("<h1>Status : Caucho Servlet Engine</h1>\n", r);

  if (config->error) {
    char buf[8192];
    escape_html(buf, config->error);
    ap_rprintf(r, "<h2 color='red'>Error : %s</h2>\n", buf);
  }
  
  jvm_status(config, r);

  ap_rputs("<p><center><table border=2 cellspacing=0 cellpadding=2 width='80%'>\n", r);
  ap_rputs("<tr><th>Host\n", r);
  ap_rputs("    <th>url-pattern\n", r);
  
  app = config ? config->applications : 0;
  for (; app; app = app->next) {
    for (loc = app->locations; loc; loc = loc->next) {
      char port[16];
      if (app->port > 0)
        sprintf(port, ":%d", app->port);
      else
        port[0] = 0;
    
      ap_rprintf(r, "<tr bgcolor='#ffcc66'><td>%s%s%s<td>%s%s%s%s%s</tr>\n", 
                 app->host && app->host[0] ? app->host : "",
                 port,
                 *app->prefix ? app->prefix : "/",
                 loc->prefix,
                 ! loc->is_exact && ! loc->suffix ? "/*" : 
                 loc->suffix && loc->prefix[0] ? "/" : "",
                 loc->suffix ? "*" : "",
                 loc->suffix ? loc->suffix : "",
                 loc->ignore ? " (ignore)" : "");
    }
  }

  ap_rputs("</table></center>\n", r);
  ap_rputs("<hr>", r);
  ap_rprintf(r, "<em>%s<em>", VERSION);
  ap_rputs("</body></html>\n", r);
  ap_rputs("</body></html>\n", r);
  ap_kill_timeout(r);

  return OK;
}

/**
 * When a child process starts, clear the srun structure so it doesn't
 * mistakenly think the old sockets areopen.
 */
static void
cse_open_child(server_rec *server, pool *p)
{
  LOG(("[%d] open child\n", getpid()));

  cse_close_all();
}

/**
 * Close all the connections cleanly when the Apache child process exits.
 *
 * @param server the Apache server object
 * @param p the Apache memory pool for the server.
 */
static void
cse_close_child(server_rec *server, pool *p)
{
  LOG(("[%d] close child\n", getpid()));

  cse_close_all();
  
  LOG(("[%d] close child done\n", getpid()));
}

/*
 * Only needed configuration is pointer to resin.conf
 */
static command_rec caucho_commands[] = {
    {"CauchoConfigFile", cse_config_file_command, NULL, RSRC_CONF|ACCESS_CONF, TAKE1,
     "Pointer to the Caucho configuration file."},
    {"CauchoHost", cse_host_command, NULL, RSRC_CONF|ACCESS_CONF, TAKE12,
     "Servlet runner host."},
    {"CauchoBackup", cse_backup_command, NULL, RSRC_CONF|ACCESS_CONF, TAKE12,
     "Servlet runner backup."},
    {"CauchoErrorPage", cse_error_page_command, NULL, RSRC_CONF|ACCESS_CONF, TAKE1,
     "Error page when connections fail."},
    {NULL}
};

/*
 * Caucho right has two content handlers:
 * caucho-status: summary information for debugging
 * caucho-request: dispatch a Caucho request
 */
static const handler_rec caucho_handlers[] =
{
    {"caucho-status", caucho_status},
    {"caucho-request", caucho_request},
    {NULL}
};

/* 
 * module configuration
 *
 * cse_dispatch needs to be at [2] to handle the ;jsession= dispatch.
 * Any later and Apache will grab it.
 */
module caucho_module =
{
    STANDARD_MODULE_STUFF,
    cse_module_init,            /* module initializer */
    cse_create_dir_config,      /* per-directory config creator */
    NULL,                       /* dir config merger */
    cse_create_server_config,   /* server config creator */
    NULL,                       /* server config merger */
    caucho_commands,            /* command table */
    caucho_handlers,            /* [7] list of handlers */
    cse_dispatch,               /* [2] filename-to-URI translation */
    NULL,                       /* [5] check/validate user_id */
    NULL,                       /* [6] check user_id is valid *here* */
    NULL,                       /* [4] check access by host address */
    NULL,                       /* [7] MIME type checker/setter */
    NULL,                       /* [8] fixups */
    NULL,                       /* [10] logger */
    NULL,                       /* [3] header parser */
    cse_open_child,             /* apache child process init */
    cse_close_child,            /* apache child process exit/cleanup */
    NULL,                       /* [1] post read_request handling */
#ifdef EAPI
    NULL,                       /* add_module */
    NULL,                       /* del_module */
    NULL,                       /* rewrite_command */
    NULL,                       /* new_connection */
#endif    
};

