/*
 *  mod-xslt -- Copyright (C) 2002, 2003 
 *   		 Carlo Contavalli 
 *   		 <ccontavalli at masobit.net>
 *
 *  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.
 */


#include "modxslt-ap1.h"

#ifndef HTTP_RECURSION_ERROR
# define HTTP_RECURSION_ERROR HTTP_INTERNAL_SERVER_ERROR
#endif

module MODULE_VAR_EXPORT mxslt_module;

  /* Handlers:
   *   dyn handler -> "mod-xslt-dynamic" -> dynamic documents,
   *   		only if the MIME type is text/xml
   *   frc handler -> "mod-xslt-force" -> dynamic documents, 
   *   		independently from the MIME type
   *   std handler -> "mod-xslt" -> static .xml documents
   */

  /* This is global since it is initialized in childe_init
   * and used in the various handlers */
mxslt_shoot_t ap1_mxslt_global_state = MXSLT_SHOOT_INIT;
mxslt_recursion_t mxslt_global_recursion = MXSLT_RECURSION_INIT;
mxslt_table_t mxslt_global_ips = mxslt_table_init_static();

  /* Set when fixup phase should not be run */
int mxslt_global_skip_fixup = 0;
# define mxslt_ap1_fixup_enable() mxslt_global_skip_fixup=0
# define mxslt_ap1_fixup_disable() mxslt_global_skip_fixup=1
# define mxslt_ap1_fixup_isdisabled() mxslt_global_skip_fixup

typedef struct mxslt_ap1_http_store_t {
  struct in_addr ip;
  uri_components URI;
} mxslt_ap1_http_store_t;

static void mxslt_ap1_outputter(void * data, char * msg, ...) {
  request_rec * r=(request_rec *)data; 
  va_list args;
  char * str;

  va_start(args, msg);
  str=ap_pvsprintf(r->pool, msg, args); 
  va_end(args);

  ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
      	    MXSLT_NAME ": %s", str);

  return;
}

static int mxslt_ap1_http_handle(mxslt_doc_t * doc, void ** store, void * context, const char * uri) {
  mxslt_ap1_http_store_t * data=(mxslt_ap1_http_store_t *)(*store);
  request_rec * r = (request_rec *)context;
  struct hostent * hp;
  struct in_addr ** address;
  uri_components URI;
  int status;

    /* Slice up URI in pieces */
  status=ap_parse_uri_components(r->pool, uri, &URI);
  if(!status) {
    MXSLT_DEBUG("status: FALSE from ap_parse_uri_components\n");
      /* Let's see what the standard handler is able to do... */
    return MXSLT_FALSE;
  }
  
    /* Get hostname 
     * gethostbyname is not reentrant, but:
     *   apache1 is not multithreaded
     *   gethostbyname is not called as long
     *     as I need hp */
  hp=gethostbyname(URI.hostname);

  if(!hp) {
    MXSLT_DEBUG("status: SKIP - couldn't resolve hostname, %d\n", h_errno);
    return MXSLT_SKIP;
  }

  for(address=(struct in_addr **)(hp->h_addr_list); address && *address; address++) {
    MXSLT_DEBUG("looking up address: %s for %s, aka: %s\n", inet_ntoa(**address), uri, URI.hostname);

      /* XXX: how do we deal with ipv6 addresses? from gethostbyname(3): ``the only
       *      valid address type is currently AF_INET'' */
    status=mxslt_table_search(&mxslt_global_ips, NULL, (void *)((*address)->s_addr), NULL);
    if(status == MXSLT_TABLE_FOUND) {
      MXSLT_DEBUG("handling address as a sub request\n");

        /* If a store pointer was provided */ 
      if(store) {
        /* data=(mxslt_ap1_http_store_t *)ap_palloc(r->pool, sizeof(mxslt_ap1_http_store_t));*/

          /* Allocate data to pass over to the open handler if needed */
	  /* Watch out: in other parts of the code, we assume that if *store != NULL,
	   * its address won't change - mxslt_ap1_http_open */
        if(*store == NULL) {
          data=(mxslt_ap1_http_store_t *)ap_palloc(r->pool, sizeof(mxslt_ap1_http_store_t));
          *store=data; 
        }

          /* Copy data needed by the open handler */
        memcpy(&(data->URI), &URI, sizeof(uri_components));
        memcpy(&(data->ip), *address, sizeof(struct in_addr));
      }

      return MXSLT_TRUE;
    }
  }

  return MXSLT_FALSE;
}

static int mxslt_ap1_http_open(mxslt_doc_t * doc, void * store, void * context, const char ** uri, void ** retval) {
  mxslt_ap1_http_store_t * data=(mxslt_ap1_http_store_t *)store;
  uri_components URI;
  char * tmpfile, * realuri;
  mxslt_dir_config_t * config;
  request_rec * r=(request_rec *)context;
  request_rec * subr=r, * tmpreq=r;
  int status;
  int fd, reclevel=0;

    /* Get apache 1 config */
  config=mxslt_ap1_get_config(r->per_dir_config, &mxslt_module);

    /* Try to open temporary file */
  fd=mxslt_ap1_mktemp_file(r, config->tmpdir, &tmpfile); 
  if(fd < 0)
    return MXSLT_FAILURE;

    /* Remember to remove the file */
  if(config->unlink) {
    MXSLT_DEBUG1("registering cleanup: %s - %s\n", *uri, tmpfile);
    ap_register_cleanup(r->pool, tmpfile, (void (*)(void *))mxslt_remove_file, ap_null_cleanup);
  }

    /* If we have no specific data about the host we're going
     * to subprocess, get it */
  if(data) {
      /* Make sub request, and listen to redirects */
    tmpreq=mxslt_ap1_sub_request(subr, fd, &(data->ip), &(data->URI), &status);
  } else {
      /* Slice up URI in pieces */
    status=ap_parse_uri_components(r->pool, *uri, &URI);
    if(!status) {
      MXSLT_DEBUG("status: FALSE from ap_parse_uri_components\n");
      return MXSLT_FAILURE;
    }

      /* Perform sub request */
    tmpreq=mxslt_ap1_sub_request(subr, fd, NULL, &(URI), &status);
  }

  realuri=(char *)*uri;
  reclevel=mxslt_http_recurse_level(doc->state->recursion);
  while(1) {
    if(!tmpreq) {
      mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-(reclevel)); 
      return MXSLT_FAILURE;
    }

      /* Check if status is a redirect */
    if(tmpreq->status < 300 || tmpreq->status >= 400)
      break;

      /* It is. Get the real uri of the next request */
    realuri=(char *)ap_table_get(subr->headers_in, "Location");

      /* Check if we already walked the new uri */
    if(mxslt_http_recurse_allowed(doc->state->recursion, realuri) != MXSLT_OK) {
        /* Output error */
      mxslt_error(doc, "warning - maximum recursion level reached" 
		       "or url already walked: %s (%s)\n", realuri, *uri);
      ap_destroy_sub_req(tmpreq);

        /* Dump stack trace */
      mxslt_http_recurse_dump(doc->state->recursion, &mxslt_ap1_outputter, r); 

        /* Pop last walked urls */
      mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-(reclevel)); 

      return MXSLT_FAILURE;
    }

      /* Check if we can handle the uri */
    if(mxslt_ap1_http_handle(doc, &store, context, realuri) == MXSLT_FALSE) {
      mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-(reclevel)); 

        /* Uhm... can we do this? eg, if we destroy the sub request, are
	 * we sure location will survive? make sure by copying the uri in
	 * the request pool... */
      mxslt_error(doc, "warning - '%s' redirects to '%s', which is not local"
		       " - loop checks disabled - good luck\n", *uri, realuri);
      (*uri)=ap_pstrdup(r->pool, realuri);
      ap_destroy_sub_req(tmpreq);

      return MXSLT_SKIP; 
    }

      /* Open file for subrequest (it is closed after each sub request) */
      /* XXX: is this dangerous? What happens if, in the mean time, the file
       *      has been replaced by a symbolic link to something else? */
    fd=open(tmpfile, O_RDWR | O_TRUNC);
    if(fd < 0) {
      ap_destroy_sub_req(tmpreq);
      mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-(reclevel)); 

      return MXSLT_FAILURE;
    }

#ifdef OLD
     /* I don't like allocating tmpreq using subr pool, which will be destroyed
      * in a really short time */
    subr=tmpreq;

      /* Watch out! data contains the address pointed by store - however,
       * if store was modifyed above, data will point to an undefined place.
       * We assume here that _http_handle does not change it in those conditions */
    tmpreq=mxslt_ap1_sub_request(subr, fd, &(data->ip), &(data->URI), &status);
    ap_destroy_sub_req(subr);
#else
    ap_destroy_sub_req(tmpreq);
    tmpreq=mxslt_ap1_sub_request(r, fd, &(data->ip), &(data->URI), &status);
#endif

      /* Remember we already walked this uri */
    mxslt_http_recurse_push(doc->state->recursion, realuri);
  } 
    /* Forget about those urls */
  mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-(reclevel)); 

    /* Check request was succesful */
  if(tmpreq->status != HTTP_OK) {
    ap_destroy_sub_req(tmpreq);
    return MXSLT_FAILURE;
  }

    /* Try to open newly created file */
  fd=open(tmpfile, O_RDONLY); 
  if(fd == -1) {
    mxslt_error(doc, "couldn't open: %s (%s, %s, %s) - errno: %d\n", tmpfile, uri, realuri, subr->filename, errno);
    ap_destroy_sub_req(tmpreq);

    return MXSLT_FAILURE;
  }
  ap_destroy_sub_req(tmpreq);

  *retval=((void *)fd);
  return MXSLT_OK;
}

static int mxslt_ap1_http_read(mxslt_doc_t * doc, void * context, const char * buffer, int len) {
  int fd = (int)context;

  return read(fd, (char *)buffer, len);
}

static int mxslt_ap1_http_close(mxslt_doc_t * doc, void * context) {
  int fd = (int)context;

  return close(fd);
}

  /* This function is called right before the handler 
   * for the file and after deciding whetever to allow or not access
   * to it (http_request.c:process_request_internal) 
   * Decides if we need to take care of the file */
static int mxslt_ap1_fixup(request_rec * r) {
  mxslt_recursion_t * recursion=&mxslt_global_recursion;
  mxslt_dir_config_t * config;
  char * type = NULL;
  char * handler = NULL;
  int status;

  config=mxslt_ap1_get_config(r->per_dir_config, &mxslt_module);

  assert(mxslt_global_skip_fixup == 0 || mxslt_global_skip_fixup == 1);

    /* If this is not something we want to handle, return and give up */
  if(!mxslt_engine_on(config->state) || r->header_only || mxslt_ap1_fixup_isdisabled()) {
    mxslt_ap1_fixup_enable();
    return DECLINED;
  }

    /* Get type of the stuff we are going to process */
  if(r->handler) 
    type=(char *)r->handler;
  else
    type=(char *)r->content_type;

  MXSLT_DEBUG("* fixup - type: %s\n", type);

    /* Check if we need to intercept the request
     * by setting a custom handler */
  if(ap_table_get(config->filter_dynamic, type))
    handler=MXSLT_DYN_HANDLER;
  else
    if(ap_table_get(config->filter_force, type))
      handler=MXSLT_FRC_HANDLER;

    /* Ok, if we decided to use a custom handler,
     * we need to check we are allowed to recurse */
  if(handler) {
    MXSLT_DEBUG("handler: %s\n", handler);

    status=mxslt_http_recurse_allowed(recursion, r->uri);
    if(status != MXSLT_OK) {
      if(status == MXSLT_MAX_LEVEL)
        ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
            MXSLT_NAME ": maximum recursion level of " STR(MXSLT_MAX_RECURSION_LEVEL) " reached, skipping %s", r->uri);
      else
        ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
            MXSLT_NAME ": loop detected while processing %s", r->uri);
      mxslt_http_recurse_dump(recursion, &mxslt_ap1_outputter, r);

      return HTTP_RECURSION_ERROR;
    }

       /* Remember original mime type */
    ap_table_set(r->notes, MXSLT_NOTE_HANDLER, type);
    r->handler=handler;
  }

  return DECLINED;
}

static int mxslt_ap1_std_handler(request_rec * r) {
  mxslt_recursion_t * recursion=&mxslt_global_recursion;
  mxslt_dir_config_t * config;
  const char * type;
  const char * forcestyle, * defaultstyle;
  int status;

      /* If this is not something we want to handle, 
       * return and give up */
  if(r->header_only) 
    return DECLINED;

      /* If the method is not get, discard the request */
  if(r->method_number != M_GET)
    return DECLINED;

    /* Check if we already opened this file */
  status=mxslt_http_recurse_allowed(recursion, r->filename);
  if(status != MXSLT_OK) {
    if(status == MXSLT_MAX_LEVEL)
      ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
            MXSLT_NAME ": maximum recursion level of " STR(MXSLT_MAX_RECURSION_LEVEL) " reached, skipping %s", r->uri);
    else
      ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
            MXSLT_NAME ": loop detected while processing %s", r->uri);
    mxslt_http_recurse_dump(recursion, &mxslt_ap1_outputter, r);

    return HTTP_RECURSION_ERROR;
  }

  mxslt_http_recurse_push(recursion, r->filename);

    /* Get mime type of the stuff we are going to process */
  if(r->content_type) 
    type=r->content_type;
  else
    type=r->handler;

    /* Ok, get configuration and stuff we have stored in the request header */
  config=mxslt_ap1_get_config(r->per_dir_config, &mxslt_module);
  
    /* Get default style for given type */
  forcestyle=ap_table_get(config->mime_styles, type); 
  defaultstyle=ap_table_get(config->default_styles, type); 
  MXSLT_DEBUG("type: %s, handler: %s, content_type: %s\n", type, r->handler, r->content_type);

  status=mxslt_ap1_file_parse(r, r->filename, defaultstyle, forcestyle, config->rules, config->params);

  mxslt_http_recurse_pop(recursion, 1);

  return status;
}

#if 0
  /* If it is a static file, no problem... but, if it is dynamic, how do we handle it?
  ap_update_mtime(r, r->finfo.st_mtime);
  ap_table_setn(r->headers_out, "Last-Modified", ap_gm_timestr_822(r->pool, mod_time));
  ap_meets_conditions(r);
  */
#endif 

static inline void mxslt_ap1_import_headers(request_rec * where, request_rec * what) {
  array_header * harr = ap_table_elts(what->headers_out);
  table_entry * entry = (table_entry *)harr->elts;
  int i;

  ap_clear_table(where->headers_out);

  for(i=0; i < harr->nelts; i++)
    ap_table_add(where->headers_out, entry[i].key, entry[i].val);

  return;
}

static inline int mxslt_ap1_passover(request_rec * r, char * filename) {
  FILE * fin;

    /* If we return DECLINED here, the original file is
     * sent back to the client, not the parsed one! */
  fin=fopen(filename, "r");
  if(fin == NULL) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
      	    MXSLT_NAME ": couldn't re-open file `%s`", filename);
    return HTTP_INTERNAL_SERVER_ERROR;
  }
  ap_send_http_header(r);
  ap_send_fd(fin, r);
  ap_rflush(r);
  fclose(fin);

  return OK;
}

static int mxslt_ap1_dyn_handler(request_rec * r) {
  mxslt_recursion_t * recursion=&mxslt_global_recursion;
  mxslt_dir_config_t * config;
  request_rec * subr;
  char * tmpfile;
  const char * defaultstyle, * type, * forcestyle;
  int status;

    /* Ok, get configuration and stuff we have stored in the request header */
  config=mxslt_ap1_get_config(r->per_dir_config, &mxslt_module);
  type=ap_table_get(r->notes, MXSLT_NOTE_HANDLER);

    /* Check that we really got here because of the 
     * fixup handler */
  if(!type) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
		  MXSLT_NAME ": what made you think you could use " MXSLT_FRC_HANDLER "?");
    return HTTP_INTERNAL_SERVER_ERROR;
  }

    /* Get default style for given type */
  forcestyle=ap_table_get(config->mime_styles, type); 
  defaultstyle=ap_table_get(config->default_styles, type); 

  MXSLT_DEBUG("type: %s\n", type);
  MXSLT_DEBUG("forcestyle: %s\n", forcestyle);
  MXSLT_DEBUG("defaultstyle: %s\n", defaultstyle);

    /* Make sub request */
  mxslt_ap1_fixup_disable();
  subr=mxslt_ap1_sub_request_pass(r, config, r->uri, &tmpfile, &status, config->unlink);
  mxslt_ap1_fixup_enable();
  if(!subr) {
      /* Otherwise, apache believes it had an error
       * while fetching error document */
    r->status=HTTP_OK;

    return status;
  }

  MXSLT_DEBUG("subr->handler: %s\n", subr->handler);
  MXSLT_DEBUG("subr->content_type: %s\n", subr->content_type);

    /* Get content type of sub request */
  if(subr->handler) 
    type=subr->handler;
  else
    type=subr->content_type;

    /* Copy subrequest headers in new headers */
  mxslt_ap1_import_headers(r, subr);

    /* Check content type of subrequest */
  if(!type || subr->status != HTTP_OK || strncmp(type, "text/xml", str_size("text/xml")) || 
     (*(type+str_size("text/xml")) != ';' && *(type+str_size("text/xml")) != '\0')) {
    if(subr->status == HTTP_OK) {
      MXSLT_DEBUG("subr->content_type is not text/xml... discarding!\n");
      status=DONE;
    } else {
      status=subr->status;
      MXSLT_DEBUG("subrequest status: %d != 200, don't know how to parse... returning raw\n", status);
    }

    r->content_type=subr->content_type;
    mxslt_ap1_passover(r, tmpfile);
    ap_destroy_sub_req(subr);

    return OK;
  }

  ap_destroy_sub_req(subr);

    /* Request is not pushed from fixup handler because it
     * is sometimes called just to check the kind of output
     * that would be generated by the handler (directory index) */
  mxslt_http_recurse_push(recursion, r->uri);


    /* Parse document */ 
  status=mxslt_ap1_file_parse(r, tmpfile, defaultstyle, forcestyle, config->rules, config->params);
  mxslt_http_recurse_pop(recursion, 1);

  return status;
}

static int mxslt_ap1_frc_handler(request_rec * r) {
  mxslt_recursion_t * recursion=&mxslt_global_recursion;
  mxslt_dir_config_t * config;
  request_rec * subr;
  char * tmpfile;
  const char * forcestyle, * type, * defaultstyle;
  int status;

    /* Ok, get configuration and stuff we have stored in the request header */
  config=mxslt_ap1_get_config(r->per_dir_config, &mxslt_module);
  type=(char *)ap_table_get(r->notes, MXSLT_NOTE_HANDLER);

    /* If type was not specifyed, the user used MXSLT_FRC_HANDLER in 
     * the configuration file! */
  if(!type) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
		  MXSLT_NAME ": what made you think you could use " MXSLT_FRC_HANDLER "?");
    return HTTP_INTERNAL_SERVER_ERROR;
  }

    /* Get default style for given type */
  forcestyle=(char *)ap_table_get(config->mime_styles, type); 
  defaultstyle=(char *)ap_table_get(config->default_styles, type); 

  MXSLT_DEBUG("type: %s\n", type);
  MXSLT_DEBUG("style: %s\n", forcestyle);
  MXSLT_DEBUG("style: %s\n", defaultstyle);

    /* This subrequest is not protected against loops because
     * it is used just to load the current document being parsed,
     * and afaik cannot lead to loops until this document is parsed */

    /* Make sub request */
  mxslt_ap1_fixup_disable();
  subr=mxslt_ap1_sub_request_pass(r, config, r->uri, &tmpfile, &status, config->unlink);
  mxslt_ap1_fixup_enable();

  if(!subr) {
      /* See other handlers */
    r->status=HTTP_OK;
    return status;
  }

    /* Check request status */
  if(subr->status != HTTP_OK) {
    MXSLT_DEBUG("subrequest status: %d != 200, don't know how to parse... returning raw\n", status);

    r->content_type=subr->content_type;
    status=subr->status;
    mxslt_ap1_passover(r, tmpfile);
    ap_destroy_sub_req(subr);

    return status;
  }

    /* Import subrequest headers */
  mxslt_ap1_import_headers(r, subr);
  ap_destroy_sub_req(subr);

    /* Request is not pushed from fixup handler because fixup
     * is sometimes called just to check the kind of output
     * that would be generated by the handler (directory index) */
  mxslt_http_recurse_push(recursion, r->uri);

    /* Parse document */ 
  status=mxslt_ap1_file_parse(r, tmpfile, defaultstyle, forcestyle, config->rules, config->params);
  mxslt_http_recurse_pop(recursion, 1);

  return status;
}

static const handler_rec mxslt_ap1_handlers[] = {
  { MXSLT_STD_HANDLER, mxslt_ap1_std_handler },
  { MXSLT_DYN_HANDLER, mxslt_ap1_dyn_handler },
  { MXSLT_FRC_HANDLER, mxslt_ap1_frc_handler },
  { NULL, NULL }
};

static const char * mxslt_ap1_default_mime_style(cmd_parms * cmd, void * pcfg, const char * mime, const char * style) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_set(conf->default_styles, mime, style);

  return NULL;
}

static const char * mxslt_ap1_set_mime_style(cmd_parms * cmd, void * pcfg, const char * mime, const char * style) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_set(conf->mime_styles, mime, style);

  return NULL;
}

static const char * mxslt_ap1_param(cmd_parms * cmd, void * pcfg, const char * param, const char * value) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  if(!param || !value)
    return "both param and value must be specifyed";

  MXSLT_DEBUG("adding parameter (%08x): %s -> %s\n", (int)conf, param, value);
  ap_table_set(conf->params, param, value);
/*  AP1_DEBUG_DUMP_TABLE("*  params\n", conf->params); */

  return NULL;
}

static const char * mxslt_ap1_nodefault_mime_style(cmd_parms * cmd, void * pcfg, const char * mime) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_unset(conf->default_styles, mime);

  return NULL;
}

static const char * mxslt_ap1_unset_mime_style(cmd_parms * cmd, void * pcfg, const char * mime) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_unset(conf->mime_styles, mime);

  return NULL;
}

static const char * mxslt_ap1_add_filter_dynamic(cmd_parms * cmd, void * pcfg, const char * mime) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_set(conf->filter_dynamic, mime, "");

  return NULL;
}

static const char * mxslt_ap1_del_filter_dynamic(cmd_parms * cmd, void * pcfg, const char * mime) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_unset(conf->filter_dynamic, mime);

  return NULL;
}

static const char * mxslt_ap1_add_filter_force(cmd_parms * cmd, void * pcfg, const char * mime) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_set(conf->filter_force, mime, "");

  return NULL;
}

static const char * mxslt_ap1_add_rule(cmd_parms * cmd, void * pcfg, char * stylesheet, char * condition) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;
  int len;

  MXSLT_DEBUG("*** stylesheet: %s, condition: %s\n", stylesheet, condition); /* DEBUG  */
  if(*stylesheet == '"' && *(stylesheet+(len=strlen(stylesheet))-1) == '"') {
    *(stylesheet+len-1)='\0';
    stylesheet+=1;
  }

  if(*condition == '"' && *(condition+(len=strlen(condition))-1) == '"') {
    *(condition+len-1)='\0';
    condition+=1;
  }

  ap_table_set(conf->rules, stylesheet, condition);

  return NULL;
}

static const char * mxslt_ap1_del_rule(cmd_parms * cmd, void * pcfg, const char * stylesheet) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_unset(conf->rules, stylesheet);

  return NULL;
}


static const char * mxslt_ap1_del_filter_force(cmd_parms * cmd, void * pcfg, const char * mime) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;

  ap_table_unset(conf->filter_force, mime);

  return NULL;
}


static const char * mxslt_ap1_set_tmp_dir(cmd_parms * cmd, void * pcfg, const char * dir) {
  mxslt_dir_config_t * conf = (mxslt_dir_config_t *) pcfg;
  struct stat fstats;

    /* Check if parameter is valid */
    /* XXX: does this work under windows? Does anybody care? */
  if(!dir || !*dir || *dir != '/')
    return ap_pstrcat(cmd->pool, cmd->cmd->name, " directory is not absolute: ", dir, NULL);

    /* Check if directory exists */
  if(stat(dir, &fstats))
    return ap_pstrcat(cmd->pool, cmd->cmd->name, " couldn't stat directory: ", dir, NULL);

  if(!S_ISDIR(fstats.st_mode))
    return ap_pstrcat(cmd->pool, cmd->cmd->name, " are you joking? This is not a directory: ", dir, NULL);

    /* Strip any eventual slash at the end of the path name (avoid //) */
  MXSLT_DEBUG("*** dir: %s, address: %08x\n", dir, (int)conf); /* DEBUG  */
  conf->tmpdir=ap_pstrdup(cmd->pool, dir);
  if(conf->tmpdir[strlen(conf->tmpdir)-1] == '/')
    conf->tmpdir[strlen(conf->tmpdir)-1]='\0';

  return NULL;
}

static void * mxslt_ap1_create_dir_config(pool *p, char * dir) {
  mxslt_dir_config_t * nconf;

  nconf=(mxslt_dir_config_t *)ap_palloc(p, sizeof(mxslt_dir_config_t));
  if(!nconf)
    return NULL;

  nconf->params=ap_make_table(p, 0);
  nconf->mime_styles=ap_make_table(p, 0);
  nconf->default_styles=ap_make_table(p, 0);
  nconf->filter_dynamic=ap_make_table(p, 0);
  nconf->filter_force=ap_make_table(p, 0);
  nconf->rules=ap_make_table(p, 0);
  nconf->state=MXSLT_STATE_UNSET;
  nconf->tmpdir=NULL;
  nconf->unlink=1;

  MXSLT_DEBUG("*** directory: %s, tmp dir set to: %s, address: %08x\n", dir, nconf->tmpdir, (int)nconf); /* DEBUG  */

  return nconf;
}

static void * mxslt_ap1_merge_dir_config(pool * p, void * basev, void * overridev) {
  mxslt_dir_config_t * base = (mxslt_dir_config_t *)basev;
  mxslt_dir_config_t * override = (mxslt_dir_config_t *)overridev;
  mxslt_dir_config_t * nconf;

  MXSLT_DEBUG("merging config (%08x, %08x)\n", (int)basev, (int)overridev);

  /*
  AP1_DEBUG_DUMP_TABLE("*  old_table\n", base->params);
  AP1_DEBUG_DUMP_TABLE("*  new_table\n", override->params); */

    /* XXX Note: ap_overlay_tables just appends one table at the end of
     *   the second table. Using the apache API, this is ok anyway because
     *   a fetch returns the _first_ matching type, and making values uniq
     *   here would require a quadratic algorithm (foreach key, foreach 
     *   inserted key). However, the params table is parsed with old
     *   values first and new value then. This is because params are then
     *   walked and inserted in a hashing table, where the last values
     *   overwrite first values... yes, this is sick. */
  nconf=(mxslt_dir_config_t *)ap_palloc(p, sizeof(mxslt_dir_config_t));
  nconf->mime_styles=ap_overlay_tables(p, override->mime_styles, base->mime_styles);
  nconf->default_styles=ap_overlay_tables(p, override->default_styles, base->default_styles);
  nconf->params=ap_overlay_tables(p, base->params, override->params);
  nconf->filter_dynamic=ap_overlay_tables(p, override->filter_dynamic, base->filter_dynamic);
  nconf->filter_force=ap_overlay_tables(p, override->filter_force, base->filter_force);
  nconf->rules=ap_overlay_tables(p, override->rules, base->rules);
  nconf->state=(override->state == MXSLT_STATE_UNSET) ? base->state : override->state;
  nconf->tmpdir=override->tmpdir ? override->tmpdir : base->tmpdir;
  nconf->unlink=override->unlink;

  /* AP1_DEBUG_DUMP_TABLE("*  gen_table\n", nconf->params); */

  return nconf;
}

#ifdef HAVE_NETWORK_IOCTLS
# include <net/if.h>

static int mxslt_ap1_setanyiplist(mxslt_table_t * ips_table) {
  struct ifconf ifc;
  int fd, status;
  struct ifreq * ifrp, ifr;

    /* Zero up structures */
  memset(&ifc, 0, sizeof(struct ifconf));
  memset(&ifr, 0, sizeof(struct ifreq));

    /* open socket */
  fd=socket(AF_INET, SOCK_DGRAM, 0);
  if(fd < 0)
    return MXSLT_FAILURE;

    /* Perform ioctl */
  status=ioctl(fd, SIOCGIFCONF, (char *)&ifc);
  if(status < 0)
    return MXSLT_FAILURE;

    /* Allocate buffer */
  ifc.ifc_buf=(char *)xmalloc(ifc.ifc_len+1);

    /* Perform ioctl */
  status=ioctl(fd, SIOCGIFCONF, (char *)&ifc);
  if(status < 0) {
    xfree(ifc.ifc_buf);
    return MXSLT_FAILURE;
  }

    /* For each interface we found */
  for(ifrp=(struct ifreq *)(ifc.ifc_buf); (char *)ifrp < (((char *)ifc.ifc_buf)+ifc.ifc_len); ifrp++) {
    strcpy(ifr.ifr_name, ifrp->ifr_name);
    ifr.ifr_addr.sa_family=AF_INET;
    status=ioctl(fd, SIOCGIFADDR, &ifr);
    if(status < 0) {
      xfree(ifc.ifc_buf);
      return MXSLT_FAILURE;
    }

    MXSLT_DEBUG("adding address: %s\n", inet_ntoa((struct in_addr)(((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr)));

      /* XXX: void * is supposed to be as big as an int, and I assume an int is at least 32 bits here */
    mxslt_table_insert(ips_table, NULL, (void *)((((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr).s_addr), NULL);
  }

  xfree(ifc.ifc_buf);

  return MXSLT_OK;
}
#endif

  /* Calculate an ip address hash */
static mxslt_table_size_t mxslt_ap1_table_hash(const char * tohash, const mxslt_table_size_t wrap) {
  return (((int)(tohash))%wrap);
}

  /* Compare two ip addresses */
static int mxslt_ap1_table_cmp(void * key1, void * key2) {
  return (int)key1 - (int)key2;
}

static void mxslt_ap1_init(server_rec * s, pool * p) {
  listen_rec * listen;
  static int inaddr_any;
  int status;

    /* Add version name to apache id */
  ap_add_version_component("mod-xslt/" MXSLT_VERSION);

    /* Initialize modxslt-library */
  mxslt_xml_load();


  if(mxslt_ap1_fixup_isdisabled()) {
    ap_log_error("mod-xslt", 0, APLOG_WARNING | APLOG_NOERRNO, s, 
		   "fixup disabled during init (%d), enabling!", mxslt_ap1_fixup_isdisabled());
    mxslt_ap1_fixup_enable();
  }

    /* Precalculate value for inaddr_any */
  inaddr_any=htonl(INADDR_ANY);

    /* Setup ip hashing table */
  mxslt_table_set_hash(&mxslt_global_ips, mxslt_ap1_table_hash);
  mxslt_table_set_cmp(&mxslt_global_ips, mxslt_ap1_table_cmp);

    /* Walk all listeners */
  MXSLT_DEBUG("ap_listeners: %08x, ap_listeners->next: %08x\n", (unsigned int)ap_listeners, (unsigned int)ap_listeners->next);
  listen=ap_listeners;
  do {
    if(listen->local_addr.sin_addr.s_addr == inaddr_any) {
      MXSLT_DEBUG("adding address (ANY): %s - %08x, %08x\n", inet_ntoa((listen->local_addr).sin_addr), 
		  (int)listen, (int)listen->next);

#ifdef HAVE_NETWORK_IOCTLS
      status=mxslt_ap1_setanyiplist(&mxslt_global_ips); 
      if(status != MXSLT_OK) {
	  /* Warn the user, but try to go on anyway */
	ap_log_error("mod-xslt", 0, APLOG_WARNING, s, 
		     "failed to fetch the ips corresponding to INADDR_ANY - please read the README!");
      }
#else 
      ap_log_error("mod-xslt", 0, APLOG_WARNING | APLOG_NOERRNO, s, 
		   "INADDR_ANY is being used without ioctl support - read mod-xslt README!");
#endif
    } else {
      MXSLT_DEBUG("adding address: %s - %08x\n", inet_ntoa((listen->local_addr).sin_addr), (int)listen);

        /* XXX: void * is supposed to be as big as an int, and I assume an int is at least 32 bits here */
      mxslt_table_insert(&mxslt_global_ips, NULL, (void *)((listen->local_addr).sin_addr.s_addr), NULL);
    }

    listen=listen->next;
  } while(listen && listen != ap_listeners);
}

static void mxslt_ap1_child_init(server_rec * s, pool *p) {
  ap_log_error("mod-xslt", 0, APLOG_DEBUG | APLOG_NOERRNO, s, "Initializing Child");
  mxslt_xml_init(&ap1_mxslt_global_state, mxslt_ap1_http_handle, mxslt_ap1_http_open, 
		 mxslt_ap1_http_close, mxslt_ap1_http_read);
}

static void mxslt_ap1_child_exit(server_rec * s, pool *p) {
  ap_log_error("mod-xslt", 0, APLOG_DEBUG | APLOG_NOERRNO, s, "Finalizing Child");
  mxslt_xml_done(&ap1_mxslt_global_state);
}

static const command_rec mxslt_ap1_cmds[] = {
  {"XSLTEngine", ap_set_flag_slot, (void *)XtOffsetOf(mxslt_dir_config_t, state), OR_OPTIONS, TAKE1, 
    	"XSLTEngine <on|off> - Set this to On to enable xslt parsing" },
  {"XSLTTmpDir", mxslt_ap1_set_tmp_dir, NULL, OR_OPTIONS, TAKE1,
    	"XSLTTmpDir <Directory> - Takes the name of a directory we can use to store temporary files"},

  {"XSLTAddFilter", mxslt_ap1_add_filter_dynamic, NULL, OR_OPTIONS, TAKE1, 
    	"XSLTAddFilter <MimeType> - Takes the mime type/handler of the files you want to filter with mod_xslt" },
  {"XSLTDelFilter", mxslt_ap1_del_filter_dynamic, NULL, OR_OPTIONS, TAKE1, 
    	"XSLTDelFilter <MimeType> - Takes the mime type/handler of the files you don't want to filter anymore with mod_xslt" },
  {"XSLTAddForce", mxslt_ap1_add_filter_force, NULL, OR_OPTIONS, TAKE1, 
    	"XSLTAddFilter <MimeType> - Takes the mime type/handler of the files you want to force filter with mod_xslt" },
  {"XSLTDelForce", mxslt_ap1_del_filter_force, NULL, OR_OPTIONS, TAKE1, 
    	"XSLTDelFilter <MimeType> - Takes the mime type/handler of the files you don't "
	"want to force filter anymore with mod_xslt" },

  {"XSLTParam", mxslt_ap1_param, NULL, OR_OPTIONS, TAKE2, 
    	"XSLTParam <Param> <Value> - Pass over this parameter to the xml parser"},

  {"XSLTAddRule", mxslt_ap1_add_rule, NULL, OR_OPTIONS, TAKE2,
        "XSLTAddRule <Stylesheet> <Rule> - Uses a given stylesheet for xml pages matching rule"},
  {"XSLTDelRule", mxslt_ap1_del_rule, NULL, OR_OPTIONS, TAKE1,
        "XSLTDelRule <Stylesheet> - Removes a previously set rule"},

  {"XSLTDefaultStylesheet", mxslt_ap1_default_mime_style, NULL, OR_OPTIONS, TAKE2, 
        "XSLTDefaultStylesheet <MimeType> <Stylesheet> - Use the provided stylesheet if no other stylesheet can be used"}, 
  {"XSLTNoDefaultStylesheet", mxslt_ap1_nodefault_mime_style, NULL, OR_OPTIONS, TAKE1, 
        "XSLTDefaultStylesheet <MimeType> - Use the provided stylesheet if no other stylesheet can be used"}, 

  {"XSLTSetStylesheet", mxslt_ap1_set_mime_style, NULL, OR_OPTIONS, TAKE2, 
    	"XSLTSetStylesheet <MimeType> <Stylesheet> - Force the usage of a particular stylesheet for a particular mime type"},
  {"XSLTUnSetStylesheet", mxslt_ap1_unset_mime_style, NULL, OR_OPTIONS, TAKE1, 
    	"XSLTUnSetStylesheet <MimeType> - Don't force the usage of a particular stylesheet anymore"},

  {"XSLTUnlink", ap_set_flag_slot, (void *)XtOffsetOf(mxslt_dir_config_t, unlink), OR_OPTIONS, TAKE1,
    	"XSLTUnlink <on|off> - Set this to off to disable deletion of temporary files"},
  {NULL}
};

module MODULE_VAR_EXPORT mxslt_module = {
  STANDARD_MODULE_STUFF,
  mxslt_ap1_init,     	     /* module initializer                  */
  mxslt_ap1_create_dir_config,    /* create per-dir    config structures */
  mxslt_ap1_merge_dir_config,     /* merge  per-dir    config structures */
  NULL,                      /* create per-server config structures */
  NULL,                      /* merge  per-server config structures */
  mxslt_ap1_cmds,                 /* table of config file commands       */
  mxslt_ap1_handlers,             /* [#8] MIME-typed-dispatched handlers */
  NULL,                      /* [#1] URI to filename translation    */
  NULL,                      /* [#4] validate user id from request  */
  NULL,                      /* [#5] check if the user is ok _here_ */
  NULL,                      /* [#3] check access by host address   */
  NULL,                      /* [#6] determine MIME type            */
  mxslt_ap1_fixup,                /* [#7] pre-run fixups                 */
  NULL,                      /* [#9] log a transaction              */
  NULL,                      /* [#2] header parser                  */
  mxslt_ap1_child_init,           /* child_init                          */
  mxslt_ap1_child_exit,           /* child_exit                          */
  NULL                       /* [#0] post read-request              */
};


