/*
 *  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-ap2.h"

  /* TSD Being used by mod-xslt */
apr_threadkey_t * mxslt_ap2_global_state;
apr_threadkey_t * mxslt_ap2_global_ectxt;
apr_threadkey_t * mxslt_ap2_global_recursion;

#if 0
  /* This is written only once... no need to
   * protect it from other threads */
mxslt_table_t mxslt_global_ips = mxslt_table_init_static();
#endif

  /* Declare apache 2 module */
module AP_MODULE_DECLARE_DATA mxslt_module;

  /* XXX Assumes eos can only be the last element of a brigade! 
   * This is not that wrong if we think that after eos no more data is read
   * and no more data can be stored in the brigade. However, APR_BRIGADE_LAST
   * is implemented by taking the previous brigade of the first element. What
   * happens if the element I've been given is not the first one? */
#define mxslt_ap2_brigade_isfinal(brigade) APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(brigade))

#if 0
  /* Handle the case of the thread being killed */
static void mxslt_state_destroy(void * state) {
#ifdef HAVE_LIBXML_THREADS
  if(state)
    xmlFreeGlobalState((xmlGlobalStatePtr)state);
#endif
}
#endif

static void mxslt_ectxt_destroy(void * state) {
  /* Empty, nothing to be done */
}

static void mxslt_recursion_destroy(void * state) {
  mxslt_http_recurse_pop((mxslt_recursion_t *)state, 
			 mxslt_http_recurse_level((mxslt_recursion_t *)state));
  xfree(state);
}

typedef struct mxslt_dir_config_t {
  apr_table_t * mime_styles;
  apr_table_t * default_styles;
  apr_table_t * params;
  apr_table_t * rules;
} mxslt_dir_config_t;

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

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

  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, MXSLT_NAME ": %s", str);
}

void mxslt_ap2_brigade_dump(apr_bucket_brigade * brigade) {
  apr_bucket * bucket;

  APR_BRIGADE_FOREACH(bucket, brigade) {
    printf("bucket: %08x, type: %08x, length: %d, start: %d, data: %08x\n", (int)bucket,
	   (int)bucket->type, (int)bucket->length, (int)bucket->start, (int)bucket->data);
  }
}

static void mxslt_ap2_child_init(apr_pool_t *p, server_rec *s) {
  mxslt_recursion_t * recursion;
  mxslt_shoot_t * state;

  MXSLT_DEBUG("initializing...\n");  

  /*
  apr_pool_cleanup_register(p, NULL, mxslt_ap2_child_exit, apr_pool_cleanup_null);

  state=apr_palloc(p, sizeof(mxslt_shoot_t));
  recursion=apr_palloc(p, sizeof(mxslt_recursion_t));
  */

    /* Ok, seems like server pool is really for the whole process,
     * not just the thread... so, use our own memory allocation routines,
     * just to make sure they are correctly freed when the thread dies */
  state=(mxslt_shoot_t *)xmalloc(sizeof(mxslt_shoot_t));
  recursion=(mxslt_recursion_t *)xmalloc(sizeof(mxslt_recursion_t));

  mxslt_state_init(state);
  mxslt_recursion_init(recursion);

  mxslt_ap2_state_set(state);
  mxslt_ap2_recursion_set(recursion);

#if 0
  mxslt_xml_init(state, mxslt_ap2_http_handle, mxslt_ap2_http_open, 
		  mxslt_ap2_http_close, mxslt_ap2_http_read);
#endif 

  mxslt_xml_init(state, NULL, NULL, NULL, NULL);

  return;
}

static apr_status_t mxslt_ap2_in_filter(ap_filter_t *f, apr_bucket_brigade *brigade) {
  f->r->no_local_copy=1;
  return APR_SUCCESS;
}

static apr_status_t mxslt_ap2_out_filter(ap_filter_t *f, apr_bucket_brigade *brigade) {
  mxslt_dir_config_t * conf=mxslt_ap2_get_config(f->r->per_dir_config, &mxslt_module);
  apr_bucket_brigade * data;
  mxslt_recursion_t * recursion;
  apr_status_t status;
  const char * forcestyle, * defaultstyle;

  apr_table_unset(f->r->headers_out, "Content-Length");

  if(f->r->header_only)           
    return ap_pass_brigade(f->next, brigade);

    /* This should cleanly handle redirects and stuff 
     * like that */
  if(f->r->status != HTTP_OK)
    return ap_pass_brigade(f->next, brigade);

  data=(apr_bucket_brigade *)f->ctx;
  ap_save_brigade(f, &data, &brigade, f->r->pool);
  f->ctx=data;

  if(!mxslt_ap2_brigade_isfinal(data)) 
    return APR_SUCCESS;

    /* Get any mime style that may have been specifyed
     * with httpd.conf parameters */
  forcestyle=(char *)apr_table_get(conf->mime_styles, f->r->content_type);
  defaultstyle=(char *)apr_table_get(conf->default_styles, f->r->content_type);

    /* Ok, prepare to push request uri, so we avoid dangerous loops */
  status=mxslt_ap2_recursion_get(&recursion);
  if(status != APR_SUCCESS) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, MXSLT_NAME ": failed to fetch thread recursion data. Killing request.");
    status=HTTP_INTERNAL_SERVER_ERROR;
    goto error;
  }

    /* Ok, maybe this is the worker model... which calls child
     * init only once per child, but where every child has many threads ... 
     * so, this is the first time we got called for a thread... prepare global
     * variables ... */
  if(!recursion) {
    mxslt_ap2_child_init(f->r->server->process->pool, f->r->server);

    status=mxslt_ap2_recursion_get(&recursion);
    if(status != APR_SUCCESS) {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, MXSLT_NAME ": failed to fetch thread recursion data. Killing request.");
      status=HTTP_INTERNAL_SERVER_ERROR;
      goto error;
    }
  }

    /* Verify we are allowed to walk this uri */
  status=mxslt_http_recurse_allowed(recursion, f->r->uri);
  if(status != MXSLT_OK) {
    if(status == MXSLT_MAX_LEVEL) 
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, 
  	   MXSLT_NAME ": maximum recursion level of " STR(MXSLT_MAX_RECURSION_LEVEL) " reached, skipping %s", f->r->uri);
    else
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, 
	   MXSLT_NAME ": loop detected while processing %s", f->r->uri);
    mxslt_http_recurse_dump(recursion, &mxslt_ap2_outputter, f->r);
      
    status=HTTP_INTERNAL_SERVER_ERROR;
    goto error;
  }

    /* Parse data, avoiding loops */
  mxslt_http_recurse_push(recursion, f->r->uri);
  status=mxslt_ap2_file_parse(f, data, defaultstyle, forcestyle, conf->rules, conf->params);
  mxslt_http_recurse_pop(recursion, 1);

  if(status == HTTP_OK)
    return ap_pass_brigade(f->next, f->ctx);

error:
/*  f->r->status=status;*/
  return status;
}

# if 0
static apr_status_t mxslt_ap2_child_exit(void * data) {
#endif
static void mxslt_ap2_child_exit(void * data) {
  MXSLT_DEBUG("exiting...\n");
  int status;

  status=mxslt_ap2_state_get(&data);
  if(status == APR_SUCCESS) {
    MXSLT_DEBUG("data being succesfully deallocated\n");
    mxslt_xml_done(data);
  }

  xfree(data);
  return;
}

#if 0
  return APR_SUCCESS;
}

typedef struct mxslt_ap2_http_store_t {
  apr_sockaddr_t * sa;
  apr_uri_t URI;
} mxslt_ap2_http_store_t;

static int mxslt_ap2_http_handle(mxslt_doc_t * doc, void ** store, void * context, const char * uri) {
  mxslt_ap2_http_store_t * data=(mxslt_ap2_http_store_t *)(*store);
  request_rec * r = (request_rec *)context;
  /* struct hostent * hp;*/
  /* uri_components URI; */
  /* struct in_addr ** address;*/
  apr_sockaddr_t * sa;
  int status;
#ifdef MXSLT_DO_DEBUG
  char ip_address[] = "255.255.255.255";
#endif

  apr_uri_t URI;

  status=apr_uri_parse(r->pool, uri, &URI);
  if(status != APR_SUCCESS) {
    MXSLT_DEBUG("status: FALSE from ap_parse_uri_components\n");
    return MXSLT_FALSE;
  }

  status=apr_sockaddr_info_get(&sa, URI.hostname, APR_INET, URI.port, 0, r->pool);
  if(status != APR_SUCCESS) {
    MXSLT_DEBUG("status: SKIP - couldn't resolve hostname\n");
    return MXSLT_SKIP;
  }

  for(; sa; sa=sa->next) {
      /* Ok, output debug information */
    MXSLT_DEBUG("looking up address: %s for %s, aka: %s\n", 
	apr_inet_ntop(sa->family, (char *)(sa->ipaddr_ptr), ip_address, sizeof(ip_address)), uri, URI.hostname);

      /* XXX: how do we deal with ipv4 addresses? here, we asked only for APR_INET */
    status=mxslt_table_search(&mxslt_global_ips, NULL, (char *)(sa->ipaddrd_ptr), NULL);
    if(status == MXSLT_TABLE_FOUND) {
      MXSLT_DEBUG("handling address as a sub request\n");
     
      if(store) {
	if(*store == NULL) {
	  data=(mxslt_ap2_http_store_t *)ap_palloc(r->pool, sizeof(mxslt_ap2_http_store_t));
	  *store=data;
	}

	memcpy(&(data->URI), &URI, sizeof(apr_uri_t));
	data->sa=sa;
      }

      return MXSLT_TRUE;
    }
  }

  return MXSLT_FALSE;
}

static request_rec * mxslt_ap2_sub_request(request_rec * curr, apr_sockaddr_t * local_addr, 
					   apr_uri_t * uri, int * toret) {
  request_rec * retval;
  request_rec store_req;
  conn_rec store_conn;

  int status;

  store_req.hostname=curr->hostname;
  store_req.headers_in=curr->headers_in;
  store_req.connection=curr->connection;

  curr->hostname=uri->hostname;
  curr->headers_in=ap_make_table(curr->pool, 5);

  if(local_addr) {
    memcpy(&store_conn, curr->connection, sizeof(conn_rec));
    store_conn.local_addr=local_addr;
    curr->connection=&store_conn;
    ap_update_vhost_given_ip(curr->connection);
    ap_update_vhost_from_headers(curr);
  }

  MXSLT_DEBUG("uri: path - %s, query - %s, args - %s\n", uri->path, uri->query, curr->args);
  retval=(request_rec *)ap_sub_req_method_uri("GET", uri->path, curr);
  retval->args=uri->query;

    /* Register output filter so we accumulate the output before sending it */
  {
    ap_filter_rec_t filter = {
      /* name */ "accumulator",
      /* filter_func */ {
	/* ap_out_filter_func */
	/* ap_in_filter_func */
      },
      /* filter_init_func */
      /* ftype */ AP_FTYPE_CONTENT,
      /* next */ NULL
    };
    ap_filter_t * handler;
    handler=ap_add_output_filter_handle(&filter, ctx, retval, retval->connection);
    ap_remove_output_filter(handler);
  }

    /* Check the fixup handler */
  if(retval->status == OK || ap_is_HTTP_SUCCESS(retval->status)) 
    status=ap_run_sub_req(retval);

    /* Uhm... this shouldn't be necessary */
  /* curr->status_line=ap_pstrdup(curr->pool, retval->status_line); */
  /* curr->status=subr->status; */

  ap_rflush(subr);

  curr->connection=store_req.connection;
  curr->headers_in=store_req.headers_in;
  curr->hostname=store_req.hostname;
   
  if(status != OK && status != DONE) {
    *toret=status;

    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, curr, MXSLT_NAME ": subrequest failed, with status %d!", status);
    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, curr, MXSLT_NAME ": couldn't fetch '%s'", url);
    ap_destroy_sub_req(subr);

    return NULL;
  }

  return subr;
}

static int mxslt_ap2_http_open(mxslt_doc_t * doc, void * store, void * context, const char ** url, void ** retval) {
  mxslt_ap2_http_store_t * data=(mxslt_ap2_http_store_t *)store;
  mxslt_dir_config_t * config;

  apr_sockaddr_t * local_addr=NULL;
  request_rec * curr=((ap_filter_t *)(context))->r;
  request_rec * subr=curr, tmpreq=r;

  int status;

  int reclevel;
  char * realuri;

  apr_uri_t * uri;
  apr_uri_t URI;

    /* Check if data was provided */
  if(data) {
    uri=&(data->URI);
    local_addr=data->sa;
  } else {
    status=apr_uri_parse(r->pool, url, &URI);
    if(!status) {
      MXSLT_DEBUG("status: FALSE from apr_uri_parse\n");
      return MXSLT_FAILURE;
    }

    uri=&URI;
  }

    /* Setup initial request */
  tmpreq=mxslt_ap1_sub_request(subr, local_addr, uri, &status);
  realuri=(char *)*url;
  reclevel=mxslt_http_recurse_level(doc->state->recursion);

    /* Walk through all redirections */
  while(1) {
      /* Check if last sub request failed */
    if(!tmpreq) {
      mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-reclevel);
      return MXSLT_FAILURE;
    }

      /* If this is not a redirect, just leave */
    if(!ap_is_HTTP_REDIRECT(tmpreq->status))
      break;

      /* It is, parse the new uri */
    realuri=(char *)apr_table_get(tmpreq->headers_in, "Location");

      /* Verify we didn't already parse the uri */
    if(mxslt_http_recurse_allowed(doc->state->recursion, realuri) != MXSLT_OK) {

        /* Output the error */
      mxslt_error(doc, "warning - maximum recursion level reached"
		  	"or url already walked: %s (%s)\n", realuri, *url);

      ap_destroy_sub_req(tmpreq);

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

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

      return MXSLT_FAILURE;
    }

      /* Ok, check if the new uri is local or remote */
    if(mxslt_ap2_http_handle(doc, &store, context, realuri) == MXSLT_FALSE) {
      mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-(reclevel));
      mxslt_error(doc, "warning - '%s' redirects to '%s', which is not local"
		       " - loop checks disabled - good luck\n", *url, realuri);
      (*url)=ap_pstrdup(curr->pool, realuri);
      ap_destroy_sub_req(tmpreq);

      return MXSLT_SKIP;
    }

      /* Destroy previous request */
    ap_destroy_sub_req(tmpreq);
    tmpreq=mxslt_ap2_sub_request(curr, store->ip, &(store->URI), &status);

      /* Remember this url was already walked */
    mxslt_http_recurse_push(doc->state->recursion, realuri);
  }

    /* Forget about all the urls we walked */
  mxslt_http_recurse_pop(doc->state->recursion, (mxslt_http_recurse_level(doc->state->recursion))-reclevel);

    /* Check the request was successful */
  if(!ap_is_HTTP_SUCCESS(tmpreq->status)) {
    ap_destroy_sub_req(tmpreq);
    return MXSLT_FAILURE;
  }

  *retval=tmpreq;
  return MXSLT_OK;
}

static int mxslt_ap2_http_read(mxslt_doc_t * doc, void * context, const char * buffer, int len) {
}

static int mxslt_ap2_http_close(mxslt_doc_t * doc, void *  context) {
}
#endif



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

static int mxslt_ap2_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

#if 0
static mxslt_table_size_t mxslt_ap2_table_hash(const char * tohash, const mxslt_table_size_t wrap) { 
  return (((int)(tohash))%wrap);
}

static mxslt_table_size_t mxslt_ap2_table_cmp(void * key1, void * key2) { 
  return (int)key1 - (int)key2;
}

#endif

  /* mxslt_register_hooks is called only once, when 
   * the is started */
static void mxslt_register_hooks(apr_pool_t *p) {
  static int inaddr_any;

  MXSLT_DEBUG("called!\n");

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

    /* Initialize modxslt-library once */
  mxslt_xml_load();
    
    /* Precalculate value for inaddr_any */
  inaddr_any=htonl(INADDR_ANY);

#if 0
    /* Prepare hashing table */
  mxslt_table_set_hash(&mxslt_global_ips, mxslt_ap2_table_hash);
  mxslt_table_set_cmp(&mxslt_global_ips, mxslt_ap2_table_cmp);

  MXSLT_DEBUG("ap_listeners: %08x, ap_listeners->next: %08x\n", 
	      (unsigned int)ap_listeners, (unsigned int)ap_listeners->next);

    /* Walk all listeners and add their addresses up */
  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_ap2_setanyiplist(&mxslt_global_ips);
      if(status != MXSLT_OK) {
          /* Warn the user, but try to go on anyway */
        ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, 
		     "failed to fetch the ips corresponding to INADDR_ANY - please read the README!");
      }
#else
      ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0,
                   "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);
#endif
  
    /* Prepare TSDs */
  if(apr_threadkey_private_create(&mxslt_ap2_global_state, mxslt_ap2_child_exit, p) ||
     apr_threadkey_private_create(&mxslt_ap2_global_ectxt, mxslt_ectxt_destroy, p) ||
     apr_threadkey_private_create(&mxslt_ap2_global_recursion, mxslt_recursion_destroy, p)
     /* apr_threadkey_private_create(&mxslt_ap2_global_ips, mxslt_ips_destroy, p) */) {
    ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, NULL,
	 	 MXSLT_NAME ": failed allocating thread specific data. Killing myself.");
    return;
  }

    /* Register real hooks */
  /* ap_hook_child_init(mxslt_ap2_child_init, NULL, NULL, APR_HOOK_MIDDLE); */
  ap_register_output_filter(MXSLT_STD_HANDLER, mxslt_ap2_out_filter, NULL, AP_FTYPE_RESOURCE);
  ap_register_input_filter(MXSLT_STD_HANDLER, mxslt_ap2_in_filter, NULL, AP_FTYPE_RESOURCE);

  return;
}

static void *mxslt_create_dir_config(apr_pool_t *p, char *dir) {
  mxslt_dir_config_t * nconf=(mxslt_dir_config_t *)apr_palloc(p, sizeof(mxslt_dir_config_t));

  MXSLT_DEBUG("called: %08x!\n", (int)nconf);

  nconf->mime_styles=apr_table_make(p, 0);
  nconf->default_styles=apr_table_make(p, 0);
  nconf->params=apr_table_make(p, 0);
  nconf->rules=apr_table_make(p, 0);

  return nconf;
}

static void *mxslt_merge_dir_config(apr_pool_t *p, void *basev, void *overridesv) {
  mxslt_dir_config_t * base = (mxslt_dir_config_t *)basev;
  mxslt_dir_config_t * override = (mxslt_dir_config_t *)overridesv;
  mxslt_dir_config_t * nconf = (mxslt_dir_config_t *)apr_palloc(p, sizeof(mxslt_dir_config_t));

    /* XXX if you are wondering why params are merged in the wrong order,
     *     take a look to the comment in  this same function in the apache API */
  nconf->mime_styles=apr_table_overlay(p, override->mime_styles, base->mime_styles);
  nconf->default_styles=apr_table_overlay(p, override->default_styles, base->default_styles);
  nconf->params=apr_table_overlay(p, base->params, override->params);
  nconf->rules=apr_table_overlay(p, override->rules, base->rules);

  MXSLT_DEBUG("called: %08x!\n", (int)nconf);
  return nconf;
}

static const char * mxslt_ap2_param(cmd_parms *cmd, void * pcfg, const char * parameter, const char * value) {
  mxslt_dir_config_t * config;

  if(!parameter || !value)
    return "both parameter and value must be set";
  
  config=(mxslt_dir_config_t *)pcfg;
  apr_table_set(config->params, parameter, value);

  return NULL;
}

static const char * mxslt_ap2_add_rule(cmd_parms *cmd, void * pcfg, const char * url, const char * rule) {
  mxslt_dir_config_t * config;

  if(!url || !rule)
    return "both parameter and value must be set";
  
  config=(mxslt_dir_config_t *)pcfg;
  apr_table_set(config->rules, url, rule);

  return NULL;
}

static const char * mxslt_ap2_del_rule(cmd_parms *cmd, void *pcfg, const char *stylesheet) {
  mxslt_dir_config_t * config; 
  
  config=(mxslt_dir_config_t *)pcfg;
  apr_table_unset(config->rules, stylesheet);

  return NULL;
}


static const char * mxslt_ap2_default_stylesheet(cmd_parms *cmd, void *pcfg, const char *mime, const char *style) {
  mxslt_dir_config_t * config;

  config=(mxslt_dir_config_t *)pcfg;
  apr_table_set(config->default_styles, mime, style);

  return NULL;
}

static const char * mxslt_ap2_set_stylesheet(cmd_parms *cmd, void *pcfg, const char *mime, const char *style) {
  mxslt_dir_config_t * config;

  config=(mxslt_dir_config_t *)pcfg;
  apr_table_set(config->mime_styles, mime, style);

  return NULL;
}

static const char * mxslt_ap2_unset_stylesheet(cmd_parms *cmd, void *pcfg, const char *mime) {
  mxslt_dir_config_t * config; 
  
  config=(mxslt_dir_config_t *)pcfg;
  apr_table_unset(config->mime_styles, mime);

  return NULL;
}

static const char * mxslt_ap2_nodefault_stylesheet(cmd_parms *cmd, void *pcfg, const char *mime) {
  mxslt_dir_config_t * config; 
  
  config=(mxslt_dir_config_t *)pcfg;
  apr_table_unset(config->default_styles, mime);

  return NULL;
}

static const command_rec mxslt_cmds[] = {
  AP_INIT_TAKE2("XSLTParam", mxslt_ap2_param, NULL, 
		OR_OPTIONS, "XSLTParam <Param> <Value>"),
  AP_INIT_TAKE2("XSLTAddRule", mxslt_ap2_add_rule, NULL, 
		OR_OPTIONS, "XSLTAddRule <Stylesheet> <Condition>"),
  AP_INIT_TAKE2("XSLTDelRule", mxslt_ap2_del_rule, NULL, 
		OR_OPTIONS, "XSLTDelRule <Stylesheet>"),
  AP_INIT_TAKE2("XSLTSetStylesheet", mxslt_ap2_set_stylesheet, NULL, 
		OR_OPTIONS, "XSLTSetStylesheet <MimeType> <Stylesheet>"),
  AP_INIT_TAKE2("XSLTDefaultStylesheet", mxslt_ap2_default_stylesheet, NULL, 
		OR_OPTIONS, "XSLTDefaultStylesheet <MimeType> <Stylesheet>"),
  AP_INIT_TAKE1("XSLTUnSetStylesheet", mxslt_ap2_unset_stylesheet, NULL, 
		OR_OPTIONS, "XSLTUnSetStylesheet <MimeType>"),
  AP_INIT_TAKE1("XSLTNoDefaultStylesheet", mxslt_ap2_nodefault_stylesheet, NULL, 
		OR_OPTIONS, "XSLTNoDefaultStylesheet <MimeType>"),
  {NULL}
};

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA mxslt_module = {
  STANDARD20_MODULE_STUFF, 
  mxslt_create_dir_config,      /* create per-dir    config structures */
  mxslt_merge_dir_config,       /* merge  per-dir    config structures */
  NULL,                    /* create per-server config structures */
  NULL,                    /* merge  per-server config structures */
  mxslt_cmds,                   /* table of config file commands       */
  mxslt_register_hooks    /* register hooks                      */
};

