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

/*
 * config.c is responsible for scanning the parsed registry and grabbing
 * relevant data.
 *
 * Important data include the web-app and the servlet-mapping so any filter
 * can properly dispatch the request.
 *
 * Also, config.c needs to grab the srun and srun-backup blocks to properly
 * send the requests to the proper JVM.
 */

#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <dirent.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/stat.h>
#include "cse.h"

#define CACHE_SIZE 16384

typedef struct hash_t {
  char *host;
  int port;
  char *uri;
  int is_match;
  int count;
} hash_t;

static hash_t g_url_cache[CACHE_SIZE];

static location_t *
cse_add_unique_location(web_app_t *app, char *prefix,
                        char *suffix, int is_exact, int ignore)
{
  config_t *config = app->config;
  location_t *loc;

  for (loc = app->locations; loc; loc = loc->next) {
    if (is_exact != loc->is_exact)
      continue;
    else if ((prefix == 0) != (loc->prefix == 0))
      continue;
    else if (prefix && strcmp(prefix, loc->prefix))
      continue;
    else if ((suffix == 0) != (loc->suffix == 0))
      continue;
    else if (suffix && strcmp(suffix, loc->suffix))
      continue;

    return loc;
  }

  loc = (location_t *) cse_alloc(config->p, sizeof(location_t));

  memset(loc, 0, sizeof(location_t));

  loc->next = app->locations;
  app->locations = loc;

  loc->application = app;
  loc->prefix = prefix;
  loc->suffix = suffix;
  loc->is_exact = is_exact;
  loc->ignore = ignore;

  /*
  LOG(("loc %s:%d %s %s %x %s\n", 
       loc->host ? loc->host : "(null)",
       loc->port,
       loc->prefix ? loc->prefix : "(null)",
       loc->suffix ? loc->suffix : "(null)",
       loc->next,
       loc->ignore ? "ignore" : ""));
  */

  return loc;
}

static web_app_t *
cse_add_web_app(config_t *config, char *host, int port, char *prefix)
{
  web_app_t *app;

  if (! host)
    host = "";
  else if (! strcmp(host, "*"))
    host = "";

  if (! prefix)
    prefix = "";
  else if (! strcmp(prefix, "/"))
    prefix = "";
  
  for (app = config->applications; app; app = app->next) {
    if ((host == 0) != (app->host == 0))
      continue;
    else if (host && strcmp(host, app->host))
      continue;
    else if (port != app->port)
      continue;
    else if (strcmp(prefix, app->prefix))
      continue;

    return app;
  }

  app = (web_app_t *) cse_alloc(config->p, sizeof(web_app_t));

  memset(app, 0, sizeof(web_app_t));

  app->next = config->applications;
  config->applications = app;
  app->config = config;

  app->host = host ? host : "";
  app->port = port;
  app->prefix = cse_strdup(config->p, prefix);

  LOG(("new web-app host:%s:%d path:%s\n", app->host, app->port, app->prefix));

  return app;
}

/**
 * Add an application pattern to the list of recognized locations
 *
 * @param config the configuration
 * @param host the host for the pattern
 * @param prefix the web-app prefix
 *
 * @return the new application
 */
static web_app_t *
cse_add_application(config_t *config, char *host, char *prefix)
{
  char *port_string;

  char *loc_host;
  int loc_port = 0;
  char loc_prefix[8192];
  int i, j;
  
  if (host) {
    host = cse_strdup(config->p, host);
    for (i = 0; host[i]; i++)
      host[i] = tolower(host[i]);
  }

  loc_host = host ? cse_strdup(config->p, host) : 0;
  if (loc_host && (port_string = strchr(loc_host, ':'))) {
    *port_string = 0;
    loc_port = atoi(port_string + 1);
  }

  i = 0;
  if (prefix && *prefix && *prefix != '/')
    loc_prefix[i++] = '/';
    
#ifdef WIN32
  if (prefix) {
    for (j = 0; prefix[j]; j++)
      loc_prefix[i++] = tolower(prefix[j]);
  }
#else
  if (prefix) {
    for (j = 0; prefix[j]; j++)
      loc_prefix[i++] = prefix[j];
  }
#endif
  loc_prefix[i] = 0;

  return cse_add_web_app(config, loc_host, loc_port, loc_prefix);
}

/**
 * Add a location pattern to the list of recognized locations
 *
 * @param app the containing application
 * @param pattern the url-pattern to match
 *
 * @return the new location
 */
static void
cse_add_location(web_app_t *app, char *pattern, char *servlet_name)
{
  config_t *config = app->config;
  char cleanPrefix[4096];
  int prefixLength;
  int cleanLength;

  char *loc_prefix = 0;
  char *loc_suffix = 0;
  int loc_is_exact = 0;

  int ignore = 0;
    
#ifdef WIN32
  if (pattern) {
    int i;
    pattern = cse_strdup(config->p, pattern);
    for (i = 0; pattern[i]; i++)
      pattern[i] = tolower(pattern[i]);
  }
#endif /* WIN32 */

  cleanPrefix[0] = 0;

  if (pattern[0] && pattern[0] != '/' && pattern[0] != '*')
    strcpy(cleanPrefix, "/");

  prefixLength = strlen(cleanPrefix);
  if (prefixLength > 0 && cleanPrefix[prefixLength - 1] == '/')
    cleanPrefix[prefixLength - 1] = 0;

  if (! pattern[0]) {
    loc_prefix = cse_strdup(config->p, cleanPrefix);
    loc_suffix = 0;
  }
  else if (pattern[0] == '*') {
    loc_prefix = cse_strdup(config->p, cleanPrefix);
    loc_suffix = pattern + 1;
  }
  else {
    if (pattern[0] != '/')
      strcat(cleanPrefix, "/");
    strcat(cleanPrefix, pattern);

    cleanLength = strlen(cleanPrefix);

    if (strlen(pattern) <= 1)
      cleanPrefix[cleanLength - 1] = 0;
    else if (cleanPrefix[cleanLength - 1] != '*')
      loc_is_exact = 1;
    else if (cleanLength >= 2 && cleanPrefix[cleanLength - 2] == '/')
      cleanPrefix[cleanLength - 2] = 0;
    else if (cleanLength > 1)
      cleanPrefix[cleanLength - 1] = 0;

    loc_prefix = cse_strdup(config->p, cleanPrefix);
    loc_suffix = 0;
  }

  if (servlet_name && ! strcmp(servlet_name, "plugin_ignore"))
    ignore = 1;

  cse_add_unique_location(app, loc_prefix, loc_suffix, loc_is_exact, ignore);
}

/**
 * Returns true if the path is an absolute path
 */
static int
is_path_absolute(char *path)
{
  return (path[0] == '/' || path[0] == '\\' ||
          (path[1] == ':' &&
           ((path[0] >= 'a' && path[0] <= 'z') ||
            (path[0] >= 'A' && path[0] <= 'Z'))));
}

static char *
cse_normalize_path(config_t *config, char *pwd, char *path)
{
  char buf[4096];
  
  if (is_path_absolute(path))
    return path;

  sprintf(buf, "%s/%s", pwd, path);
  
  return cse_strdup(config->p, buf);
}

static char *
cse_app_dir(config_t *config, registry_t *node, char *parent,
            char *war_expand_dir)
{
  char buf[4096];
  char *value;
  char *dot;

  if (! node)
    return parent;
  
  value = cse_find_value(node->first, "app-dir");
  
  if (! parent || ! *parent)
    parent = ".";

  if ((! value || ! value[0]) && ! strcmp(node->key, "web-app")) {
    value = node->value;
    
    if (value && value[0] == '/')
      value++;
  }
  
  if (! value || ! value[0]) {
    /* LOG(("app-dir(p) %s\n", parent)); */
    return parent;
  }

  /* Handle war expansion */
  dot = strrchr(value, '.');
  if (war_expand_dir && dot &&
      (! strcmp(dot, ".war") || ! strcmp(dot, ".jar"))) {
    char buf[1024];
    int p;
    int i;

    strcpy(buf, war_expand_dir);
    strcat(buf, "/_");
    i = strlen(buf);
    
    for (p = dot - value; p >= 0; p--) {
      if (value[p] == '/' || value[p] == '\\')
        break;
    }
    p++;

    for (; p < dot - value; p++)
      buf[i++] = value[p];
    buf[i] = 0;
    
    /* LOG(("app-dir(w) %s\n", buf)); */
    
    return strdup(buf);
  }

  /* XXX: in theory only for win32 */
  if (is_path_absolute(value)) {
    /* LOG(("app-dir(a) %s\n", value)); */
    return value;
  }

  /* since this only happens on startup, the memory leak is okay */
  sprintf(buf, "%s/%s", parent, value);
  
  /* LOG(("app-dir(r) %s\n", buf)); */
  
  return cse_strdup(config->p, buf);
}

static void
cse_init_web_app_contents(web_app_t *app, registry_t *node)
{
  /*
   * The following can't be added automatically since it takes control
   * away from the configuration and breaks some user's configurations.
   */
  /*
   * cse_add_location(config, host, prefix, "*.jsp", 0);
   * cse_add_location(config, host, prefix, "*.xtp", 0);
   */
  /* WEB-INF passes to Resin so Resin can hide it. */
  cse_add_location(app, "/WEB-INF*", 0);
  
  for (; node; node = node->next) {
    if (! strcmp(node->key, "servlet-mapping")) {
      registry_t *url_pattern = cse_next_link(node->first, "url-pattern");
      registry_t *servlet_name = cse_next_link(node->first, "servlet-name");

      if (url_pattern && url_pattern->value)
	cse_add_location(app, url_pattern->value,
                         servlet_name ? servlet_name->value : 0);
    }
  }
}

/**
 * Adds a configuration file as a dependency.  If the file changes,
 * the plugin will reread the configuration.
 *
 * @param config the server configuration.
 * @param path filepath to the dependent file.
 */
void
cse_add_depend(config_t *config, char *path)
{
  struct stat st;
  depend_t *depend;
  
  depend = (depend_t *) cse_alloc(config->p, sizeof(depend_t));
  memset(depend, 0, sizeof(depend_t));
  depend->next = config->depend_list;
  config->depend_list = depend;
  depend->path = cse_strdup(config->p, path);

  if (! stat(path, &st)) {
    depend->last_modified = st.st_mtime;
    depend->last_size = st.st_size;
  }
}

/**
 * Initialize a web application.
 */
static void
cse_init_web_app(web_app_t *app, registry_t *node, char *app_dir)
{
  config_t *config = app->config;
  char buf[4096];
  FILE *file;
  
  cse_init_web_app_contents(app, node ? node->first : 0);
  
  if (! app_dir)
    return;

  /* XXX: should the registry itself get updated? i.e. adding this
  // node somewhere?
  */
  sprintf(buf, "%s/WEB-INF/web.xml", app_dir);
  /* LOG(("web-app %s\n", buf)); */
  
  cse_add_depend(app->config, buf);
  
  file = fopen(buf, "r");
  if (file) {
    registry_t *web = cse_parse(file, config, cse_strdup(config->p, buf));
    fclose(file);

    if (web && web->first)
      cse_init_web_app_contents(app, web->first->first);
  }
}

/**
 * Initialize the war dir.
 */
static void
cse_init_war_dir(config_t *config, char *host, char *war_dir)
{
#ifndef WIN32
  DIR *dir;
  struct dirent *dirent;

  /* LOG(("war-dir %s\n", war_dir)); */
  
  cse_add_depend(config, war_dir);

  dir = opendir(war_dir);
  if (! dir)
    return;
  
  while ((dirent = readdir(dir))) {
    char app_dir[4096];
    char prefix[4096];
    char *name = dirent->d_name;
    struct stat st;

    if (! strcmp(name, "ROOT"))
      sprintf(prefix, "/");
    else
      sprintf(prefix, "/%s", name);

    sprintf(app_dir, "%s/%s", war_dir, name);
    
    if (! stat(app_dir, &st) && S_ISDIR(st.st_mode) &&
        name[0] != '.' && name[0] != '_') {
      web_app_t *app;
      /* LOG(("app %s\n", name)); */

      app = cse_add_application(config, host, prefix);
    
      cse_init_web_app(app, 0, app_dir);
      cse_add_location(app, "/*", 0);
    }
  }
  closedir(dir);
#else
  WIN32_FIND_DATA dir;
  HANDLE hSearch;
  char buf[8192];

  /* LOG(("war-dir %s\n", war_dir)); */
  
  cse_add_depend(config, war_dir);

  sprintf(buf, "%s/*", war_dir);
  hSearch = FindFirstFile(buf, &dir);
  if (hSearch != INVALID_HANDLE_VALUE) {
	  do {
	    char app_dir[4096];
	    char prefix[4096];
		char *name = dir.cFileName;
                web_app_t *app;
	    struct stat st;

		if (! strcmp(name, "ROOT"))
			sprintf(prefix, "/");
		else
		   sprintf(prefix, "/%s", name);

		sprintf(app_dir, "%s/%s", war_dir, name);
    
		if (! stat(app_dir, &st) && (st.st_mode & _S_IFDIR) &&
			name[0] != '.' && name[0] != '_') {
			/* LOG(("app %s\n", name)); */
    
			app = cse_add_application(config, 0, host, prefix);
                        cse_init_web_app(app, 0, app_dir);
			cse_add_location(app, "/*", 0);
		}
	  } while (FindNextFile(hSearch, &dir));
  }
#endif
}


/**
 * Initialize the configuration mapping for the <host> block.
 *
 * @param config configuration structure holding the parsed mapping.
 * @param node the configuration node for the <host> block.
 * @param host default host name.
 * @param app_dir current directory.
 */
static void
cse_init_host(config_t *config, registry_t *node, char *host, char *app_dir,
              registry_t *default_node)
{
  registry_t *host_default = node;
  char *expand_dir;
  char *host_dir;
  web_app_t *app;

  if (node && (expand_dir = cse_find_value(node->first, "war-expand-dir")))
    expand_dir = cse_normalize_path(config, config->resin_home, expand_dir);
  else if (node && (expand_dir = cse_find_value(node->first, "war-dir")))
    expand_dir = cse_normalize_path(config, config->resin_home, expand_dir);
  else {
    char buf[1024];

    sprintf(buf, "%s/_war", config->work_dir);
    expand_dir = cse_strdup(config->p, buf);
  }

  host_dir = cse_app_dir(config, node, app_dir, expand_dir);

  app = cse_add_application(config, host, "");  
  cse_init_web_app(app, node, host_dir);
  cse_init_web_app(app, default_node, 0);

  if (! node)
    return;

  for (node = node->first; node; node = node->next) {
    if (! node->value) {
    }
    else if (! strcmp(node->key, "web-app")) {
      char *prefix = node->value;
      char *subdir = cse_app_dir(config, node, host_dir, expand_dir);

      app = cse_add_application(config, host, prefix);
      cse_init_web_app(app, node, subdir);
      cse_init_web_app(app, host_default, 0);
      cse_init_web_app(app, default_node, 0);
    }
    else if (! strcmp(node->key, "war-dir") && node->value) {
      char *subdir = node->value;
      char war_dir[8192];

      if (is_path_absolute(subdir))
        sprintf(war_dir, "%s", subdir);
      else
        sprintf(war_dir, "%s/%s", config->resin_home, subdir);

      cse_init_war_dir(config, host, expand_dir);
    }
  }
}

/**
 * Initialize the dispatch mappings.
 */
static void
cse_init_server(config_t *config, registry_t *node)
{
  registry_t *default_node = node;
  char *app_dir = cse_app_dir(config, node, config->resin_home, 0);

  cse_init_host(config, node, "", config->resin_home, 0);

  if (! node)
    return;

  for (node = node->first; node; node = node->next) {
    if (node->value && ! strcmp(node->key, "host")) {
      char *name = node->value;
      char *host_dir = cse_app_dir(config, node, app_dir, 0);

      cse_init_host(config, node, name, host_dir, default_node);
    }
  }
}

/**
 * Adds a new backup to the configuration
 */
srun_item_t *
cse_add_host(config_t *config, const char *hostname, int port)
{
  return cse_add_host_int(config, hostname, port, -1, "", 0, 0);
}

/**
 * Adds a new backup to the configuration
 */
srun_item_t *
cse_add_backup(config_t *config, const char *hostname, int port)
{
  return cse_add_host_int(config, hostname, port, -1, "", 1, 0);
}

/**
 * initialize a single srun host
 */
static void
cse_init_host_node(config_t *config, registry_t *node, int is_backup)
{
  int port;
  char *host;
  registry_t *host_node = cse_next_link(node, "srun-host");
  registry_t *port_node = cse_next_link(node, "srun-port");
  registry_t *index_node = cse_next_link(node, "srun-index");
  registry_t *ssl_node = cse_next_link(node, "ssl");
  registry_t *group_node = cse_next_link(node, "srun-group");
  int session;
  srun_item_t *srun_item = 0;
  srun_t *srun;
  registry_t *subnode;
  char *group = "";
  int is_ssl;

  if (! host_node)
    host_node = cse_next_link(node, "host");
  if (! port_node)
    port_node = cse_next_link(node, "port");

  if (host_node && host_node->value && *host_node->value)
    host = host_node->value;
  else
    host = "localhost";
  
  if (port_node && port_node->value && *port_node->value)
    port = atoi(port_node->value);
  else
    port = 6802;
    
  if (index_node && index_node->value && *index_node->value)
    session = atoi(index_node->value) - 1;
  else
    session = -1;
    
  if (group_node && group_node->value)
    group = group_node->value;
  else
    group = "";

  is_ssl = (ssl_node && ssl_node->value &&
            strcmp(ssl_node->value, "false") && strcmp(ssl_node->value, "no"));
  srun_item = cse_add_host_int(config, host, port,
                               session, group,
                               is_backup, is_ssl);
  if (! srun_item)
    return;

  srun = srun_item->srun;
  if (! srun)
    return;

  subnode = cse_next_link(node->parent, "connect-timeout");
  if (subnode && subnode->value && *subnode->value)
    srun->connect_timeout = atoi(subnode->value);
  subnode = cse_next_link(node, "connect-timeout");
  if (subnode && subnode->value && *subnode->value)
    srun->connect_timeout = atoi(subnode->value);
  
  subnode = cse_next_link(node->parent, "live-time");
  if (subnode && subnode->value && *subnode->value)
    srun->live_time = atoi(subnode->value);
  subnode = cse_next_link(node, "live-time");
  if (subnode && subnode->value && *subnode->value)
    srun->live_time = atoi(subnode->value);
  if (srun->live_time < 10)
    srun->live_time = 10;
  
  subnode = cse_next_link(node->parent, "dead-time");
  if (subnode && subnode->value && *subnode->value)
    srun->dead_time = atoi(subnode->value);
  subnode = cse_next_link(node, "dead-time");
  if (subnode && subnode->value && *subnode->value)
    srun->dead_time = atoi(subnode->value);
  if (srun->dead_time < 5)
    srun->dead_time = 5;
}

/**
 * Initialize all the load-balancing hosts in the configuration.
 */
static void 
cse_init_hosts(config_t *config, registry_t *top)
{
  int has_srun = 0;
  registry_t *node;
  
  for (node = cse_next_link(top, "srun");
       node;
       node = cse_next_link(node->next, "srun")) {
    has_srun = 1;
    cse_init_host_node(config, node->first, 0);
  }

  for (node = cse_next_link(top, "srun-backup");
       node;
       node = cse_next_link(node->next, "srun-backup")) {
    has_srun = 1;
    cse_init_host_node(config, node->first, 1);
  }
}

/**
 * Logging for the configuration file.
 */
void
cse_log_config(config_t *config)
{
  web_app_t *app = config->applications;

  for (; app; app = app->next) {
    location_t *loc = app->locations;
    
    for (; loc; loc = loc->next) {
      LOG(("cfg host:%s%s prefix:%s suffix:%s next:%x\n", 
           app->host ? app->host : "null",
           app->prefix ? app->prefix : "/",
           loc->prefix ? loc->prefix : "null",
           loc->suffix ? loc->suffix : "null",
           loc->next));
    }
  }
}

static void
cse_init_error_page(config_t *config, registry_t *node)
{
  for (node = cse_next_link(node, "error-page");
       node;
       node = cse_next_link(node->next, "error-page")) {
    char *exn = cse_find_value(node->first, "exception-type");
    char *code = cse_find_value(node->first, "error-code");
    
    if (exn && ! strcmp(exn, "connection"))
      config->error_page = cse_find_value(node->first, "location");
    else if (! exn && ! code && ! config->error_page)
      config->error_page = cse_find_value(node->first, "location");
  }
}

/**
 * Initialize the configuration.
 */
void
cse_init_config(config_t *p_config)
{
  registry_t *root = 0;
  registry_t *node = 0;
  FILE *is;
  char *caucho_status;
  config_t new_config = *p_config;
  config_t *config = &new_config;
  int i;

  LOG(("initializing from %s\n", config->path));

  if (! config->p)
    config->p = cse_create_pool(config);

  if (! config->resin_home) {
    int p = strlen(config->path);
    
    for (;
         p >= 0 && (config->path[p] != '/' && config->path[p] != '\\');
         p--) {
    }
    
    for (p--;
         p >= 0 && (config->path[p] != '/' && config->path[p] != '\\');
         p--) {
    }

    if (p >= 0) {
      config->resin_home = strdup(config->path);
      config->resin_home[p] = 0;
    }
    else {
      config->resin_home = ".";
    }
  }

  /*
  // XXX: in theory, should free these, but they only change during
  // development, so it really shouldn't matter
  */
  config->registry = 0;
  config->applications = 0;
  config->error = 0;

  for (i = 0; i < CACHE_SIZE; i++) {
    if (g_url_cache[i].uri) {
      free(g_url_cache[i].uri);
      free(g_url_cache[i].host);
    }
    g_url_cache[i].uri = 0;
  }
  
  config->update_count++;

  config->disable_caucho_status = 1;
  config->disable_session_failover = 0;
  config->srun_list = 0;
  config->srun_capacity = 0;
  config->srun_size = 0;
  config->depend_list = 0;
  cse_add_depend(config, config->path);

  is = fopen(config->path, "r");
  if (! is) {
    cse_error(config, "Resin can't open config file `%s'\n", config->path);
    return;
  }

  root = cse_parse(is, config, config->path);
  fclose(is);

  config->registry = root;
  /* cse_print_registry(root); */
  
  if (root)
    node = cse_next_link(root->first, "caucho.com");
  
  if (node) {
    registry_t *java = cse_next_link(node->first, "java");
    if (java) {
      char *work_dir = cse_find_value(node->first, "work-dir");

      if (! work_dir) {
      }
      else if (is_path_absolute(work_dir))
        config->work_dir = work_dir;
      else {
        char buf[1024];

        sprintf(buf, "%s/%s", config->resin_home, work_dir);
        config->work_dir = strdup(buf);
      }
    }
  }

  if (! config->work_dir) {
    char buf[1024];

    sprintf(buf, "%s/work", config->resin_home);
    config->work_dir = strdup(buf);
  }

  if (node)
    node = cse_next_link(node->first, "http-server");

  if (node) {
    registry_t *subnode;
    char *value;

    subnode = cse_next_link(node->first, "caucho-status");
    
    caucho_status = cse_find_value(node->first, "caucho-status");
    if (subnode &&
        (! caucho_status ||
         ! strcmp(caucho_status, "true") ||
         ! strcmp(caucho_status, "yes")))
      config->disable_caucho_status = 0;

    config->session_url_prefix = cse_find_value(node->first,
                                                "session-url-prefix");

    if (! config->session_url_prefix || ! config->session_url_prefix[0])
      config->session_url_prefix = ";jsessionid=";
    
    config->session_url_prefix_length = strlen(config->session_url_prefix);
    
    value = cse_find_value(node->first, "sticky-sessions");
    if (value &&
        (! strcmp(value, "false") || ! strcmp(value, "no")))
      config->disable_sticky_sessions = 1;
    
    value = cse_find_value(node->first, "sessions-failover");
    if (value &&
        (! strcmp(value, "false") || ! strcmp(value, "no")))
      config->disable_session_failover = 1;
  }

  if (node)
    cse_init_server(config, node);

  if (node)
    cse_init_hosts(config, node->first);

  if (node)
    cse_init_error_page(config, node->first);

  if (! config->lock)
    config->lock = cse_create_lock(config);

  /* XXX: need an atomic switch? */
  *p_config = *config;
}

/**
 * Check the configuration files for any changes.  If there are any changes,
 * reload the configuration files.
 */
void
cse_update_config(config_t *config, int now)
{
  struct stat st;
  depend_t *depend;
  int has_changed = 0;
  int diff = config->last_update - now;

  if (diff > -15 && diff < 15)
    return;

  config->last_update = now;
  
  for (depend = config->depend_list;
       depend && ! has_changed;
       depend = depend->next) {
    if (stat(depend->path, &st))
      has_changed = depend->last_modified != 0;
    else if (depend->last_modified != st.st_mtime)
      has_changed = 1;
    else if (depend->last_size != st.st_size)
      has_changed = 1;

    if (has_changed) {
      LOG(("%s mod:%d->%d size:%d->%d\n", depend->path,
           depend->last_modified, st.st_mtime,
           depend->last_size, st.st_size));
    }
  }

  if (has_changed) {
    cse_lock(config->lock);
    cse_close_sockets(config);
    cse_free_pool(config->p);
    cse_init_config(config);
    cse_unlock(config->lock);
  }
}


/**
 * tests if 'full' starts with 'part'
 *
 * If it's not an exact match, the next character of 'full' must be '/'.
 * That way, a servlet mapping of '/foo/ *' will match /foo, /foo/bar, but
 * not /foolish.
 */
static int
cse_starts_with(const char *full, const char *part)
{
  char ch1, ch2;

  while ((ch2 = *part++) && (ch1 = *full++) && ch1 == ch2) {
  }

  if (ch2)
    return 0;
  else if (! *full)
    return 1;
  else if (*full == '/')
	return 1;
  else
#ifdef WIN32
  /* special case so web-inf. will match */
    return *full == '.' && (full[1] == 0 || full[1] == '/' || full[1] == '\\');
#else
  return 0;
#endif
}

static int
cse_match_suffix(const char *full, const char *suffix)
{
  int len = strlen(suffix);

  do {
    char *match = strstr(full, suffix);
    if (! match)
      return 0;
    
    if (! match[len] || match[len] == '/')
      return 1;
#ifdef WIN32
    if (match[len] == '.') {
      for (; match[len] == '.'; len++) {
      }

      if (! match[len] || match[len] == '/')
     	return 1;
    }
#endif

    full = match + len;
  } while (*full);

  return 0;
}

static int
hex_to_digit(int hex)
{
  if (hex >= '0' && hex <= '9')
    return hex - '0';
  else if (hex >= 'a' && hex <= 'f')
    return hex - 'a' + 10;
  else if (hex >= 'A' && hex <= 'F')
    return hex - 'A' + 10;
  else
    return 0;
}

static int
cse_is_match(config_t *config,
             const char *raw_host, int port, const char *raw_uri,
             int unescape)
{
  char uri[16 * 1024];
  char host[1024];
  char *suburi;
  int len = sizeof(uri) - 1;
  int host_len = sizeof(host) - 1;
  web_app_t *app_ptr;
  web_app_t *app;
  int best_len;
  location_t *loc;
  int i, k;
  int ch;
  int test_ch = config->session_url_prefix[0];

  k = 0;
  for (i = 0; (ch = raw_uri[i]) && i + 1 < len; i++) {
    /* strip the session_url_prefix */
    if (ch == test_ch && ! strncmp(raw_uri + i, config->session_url_prefix,
                                   config->session_url_prefix_length)) {
      break;
    }
    
    if (ch == '%' && unescape) {
      int h1 = raw_uri[i + 1];

      if (h1 == 'u') {
        ch = hex_to_digit(raw_uri[i + 2]);
        ch = 16 * ch + hex_to_digit(raw_uri[i + 3]);
        ch = 16 * ch + hex_to_digit(raw_uri[i + 4]);
        ch = 16 * ch + hex_to_digit(raw_uri[i + 5]);

        i += 4;
      }
      else {
        ch = hex_to_digit(h1);
        ch = 16 * ch + hex_to_digit(raw_uri[i + 2]);

        i += 2;
        
        if ((ch & 0xf0) == 0xc0 && raw_uri[i + 1] == '%' && i + 3 < len) {
          int ch2 = hex_to_digit(raw_uri[i + 2]);
          ch2 = 16 * ch2 + hex_to_digit(raw_uri[i + 3]);

          if (ch2 >= 0x80) {
            ch = ((ch & 0x3f) << 6) + (ch2 & 0x3f);
            i += 3;
          }
        }
      }
    }
    else if (ch == ':')
      break;
      
    if (ch == '/' && k > 0 && (uri[k - 1] == '/' || uri[k - 1] == '\\'))
      continue;

#ifdef WIN32
    if (ch >= 0 && isupper(ch))
      uri[k++] = tolower(ch);
    else if (ch >= 0)
      uri[k++] = ch;
#else
    if (ch >= 0)
      uri[k++] = ch;
#endif
  }
  uri[k] = 0;

  for (i = 0; raw_host && (ch = raw_host[i]) && i + 1 < host_len; i++) {
    if (isupper(ch))
      host[i] = tolower(ch);
    else
      host[i] = ch;
  }

  host[i] = 0;

  best_len = 0;
  app = 0;
  for (app_ptr = config->applications; app_ptr; app_ptr = app_ptr->next) {
    /*
     * The host needs to match exactly.  strstr would match
     * caucho.com to gryffindor.caucho.com
     */
    if (app_ptr->host && *(app_ptr->host) && strcmp(app_ptr->host, host))
      continue;

    else if (port && app_ptr->port && port != app_ptr->port)
      continue;
    
    else if (! cse_starts_with(uri, app_ptr->prefix))
      continue;

    else if (strlen(app_ptr->prefix) < best_len)
      continue;

    LOG(("app-match host:%s:%d%s with host:%s uri:%s\n",
         app_ptr->host ? app_ptr->host : "null",
         app_ptr->port,
         app_ptr->prefix ? app_ptr->prefix : "",
         host, uri));

    best_len = strlen(app_ptr->prefix);
    app = app_ptr;
  }

  if (! app)
    return 0;

  suburi = uri + best_len;

  for (loc = app->locations; loc; loc = loc->next) {
    LOG(("match host:%s:%d%s prefix:%s suffix:%s with host:%s uri:%s next:%x\n", 
         app->host ? app->host : "null",
         app->port,
         app->prefix ? app->prefix : "null",
         loc->prefix ? loc->prefix : "null",
         loc->suffix ? loc->suffix : "null",
         host, uri, loc->next));

    if (loc->is_exact && ! strcmp(suburi, loc->prefix)) {
      return 1;
    }
    else if (loc->is_exact)
      continue;
      
    else if (! cse_starts_with(suburi, loc->prefix))
      continue;
    
    else if (loc->suffix && ! cse_match_suffix(suburi, loc->suffix))
      continue;

    return ! loc->ignore;
  }

  if (strstr(suburi, "/j_security_check"))
    return 1;
  
  return 0;
}


/**
 * Tests if the request matches a Resin URL.
 *
 * @param config the plugin configuration structure
 * @param host the request's host
 * @param port the request's port
 * @param url the request's uri
 */

int
cse_match_request(config_t *config, const char *host, int port,
                  const char *uri, int unescape)
{
  int hash = port;
  int i;
  int is_match = 0;
  
  hash_t *entry;
  char *test_uri;
  char *test_host;
  int test_port;
  int test_count;
  int test_match;

  if (! host)
    host = "";
  if (! uri)
    uri = "";
  
  for (i = 0; host && host[i]; i++)
    hash = 65531 * hash + host[i];
  
  for (i = 0; uri && uri[i]; i++)
    hash = 65531 * hash + uri[i];

  if (hash < 0)
    hash = -hash;
  hash = hash % CACHE_SIZE;

  entry = &g_url_cache[hash];

  test_count = entry->count;
  test_uri = entry->uri;
  test_host = entry->host;
  test_port = entry->port;
  test_match = entry->is_match;

  if (test_count != entry->count) {
  }
  else if (! test_uri || strcmp(test_uri, uri)) {
  }
  else if (! test_host || strcmp(test_host, host)) {
  }
  else if (test_port && test_port != port) {
  }
  else
    return test_match;

  is_match = cse_is_match(config, host, port, uri, unescape);

  cse_lock(config->lock);
  entry->count++;
  entry->is_match = is_match;
  if (entry->uri) {
    free(entry->host);
    free(entry->uri);
    
    entry->uri = 0;
    entry->host = 0;
  }

  LOG(("entry %s %s\n", host, uri));
  
  entry->host = strdup(host ? host : "");
  entry->uri = strdup(uri);
  entry->port = port;
  entry->count++;
  cse_unlock(config->lock);

  return is_match;
}
