/* ** mod_annodex.c -- Apache sample annodex module ** [Autogenerated via ``apxs -n annodex -g''] ** ** To play with this sample module first compile it into a ** DSO file and install it into Apache's modules directory ** by running: ** ** $ apxs -c -i mod_annodex.c ** ** Then activate it in Apache's httpd.conf file for instance ** for the URL /annodex in as follows: ** ** # httpd.conf ** LoadModule annodex_module modules/mod_annodex.so ** ** SetHandler annodex ** ** ** Then after restarting Apache via ** ** $ apachectl restart ** ** you immediately can request the URL /annodex and watch for the ** output of this module. This can be achieved for instance via: ** ** $ lynx -mime_header http://localhost/annodex ** ** The output should be similar to the following one: ** ** HTTP/1.1 200 OK ** Date: Tue, 31 Mar 1998 14:42:22 GMT ** Server: Apache/1.3.4 (Unix) ** Connection: close ** Content-Type: text/html ** ** The sample page from mod_annodex.c */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "ap_config.h" #include "http_core.h" #include "http_log.h" #include "ap_compat.h" #include #include #include /* chdir */ #include #include #include /* define __PROTOTYPE to enable mode= parameter */ /* #define __PROTOTYPE */ #define DEBUG #define ANX_MIME_TYPE "application/annodex" #define CMML_MIME_TYPE "text/cmml" #define CMML_PREAMBLE \ "\n" #define MOD_ANNODEX_TYPE_UNKNOWN -1 #define MOD_ANNODEX_TYPE_ANX 0 #define MOD_ANNODEX_TYPE_CMML 1 #ifdef __PROTOTYPE #define MOD_ANNODEX_TYPE_MEDIA 2 #endif #define MAX_CLIP_LEN 32768 /** * Print a time as npt using ap_rprintf * \param r the request record * \param seconds the time to print * \returns the ap_rprintf return value */ static int rprintf_time_npt (request_rec * r, double seconds) { int hrs, min; double sec; char * sign; sign = (seconds < 0.0) ? "-" : ""; if (seconds < 0.0) seconds = -seconds; hrs = (int) (seconds/3600.0); min = (int) ((seconds - ((double)hrs * 3600.0)) / 60.0); sec = seconds - ((double)hrs * 3600.0)- ((double)min * 60.0); /* XXX: %02.3f workaround */ if (sec < 10.0) { return ap_rprintf (r, "%s%02d:%02d:0%2.3f", sign, hrs, min, sec); } else { return ap_rprintf (r, "%s%02d:%02d:%02.3f", sign, hrs, min, sec); } } /** * Create a table corresponding to name=value pairs in the query string * @param r The resource request * @param query The query string * @return A newly created table with corresponding name=value keys. */ static table * make_cgi_table (request_rec * r, char * query) { table * t; char * key, * val, * end; t = make_table (r->pool, 3); if (!query) return t; key = query; do { val = strchr (key, '='); end = strchr (key, '&'); if (end) { if (val) { if (val < end) { *val++ = '\0'; } else { val = NULL; } } *end++ = '\0'; } else { if (val) *val++ = '\0'; } /*ap_rprintf (r, "%s = %s\n", key, val);*/ table_set (t, key, val); key = end; } while (end != NULL); return t; } /** * Determine the relative quality factor of a mime_type given the Accept: * header of a resource request, following rules of RFC2616 Sec. 14.1 * @param r The resource request * @param mime_type The mime_type to check * @return The relative quality factor */ static float get_accept_quality (request_rec * r, char * mime_type) { char * a, * accept, *next, * last, * pnext, * plast; float q = 0.0, type_q = 0.0, all_q = 0.0; char * m_sep, * m_major; size_t m_major_len; a = (char *)table_get (r->headers_in, (const char *)"Accept"); /* If there was no Accept: header, accept all types equally */ if (a == NULL) return 1.0; /* Form a 'major / *' mime type range for later comparison */ m_sep = strchr (mime_type, '/'); m_major_len = (size_t)(m_sep - mime_type); m_major = pstrndup (r->pool, mime_type, m_major_len + 2); *(m_major+m_major_len+1) = '*'; *(m_major+m_major_len+2) = '\0'; /* Copy the Accept line for tokenization */ accept = ap_pstrdup (r->pool, a); #if 0 /* XXX: ??? how to backport to apache 1.3?*/ apr_collapse_spaces (accept, accept); #endif next = strtok_r (accept, ",", &last); while (next) { pnext = strtok_r (next, ";", &plast); if (!strcmp (pnext, mime_type)) { while (pnext) { pnext = strtok_r (NULL, ";", &plast); if (pnext && sscanf (pnext, "q=%f", &q) == 1) { return q; } } return 1.0; } else if (!strcmp (pnext, "*/*")) { while (pnext) { pnext = strtok_r (NULL, ";", &plast); if (pnext && sscanf (pnext, "q=%f", &q) == 1) { all_q = q; } } all_q = 1.0; } else if (!strcmp (pnext, m_major)) { while (pnext) { pnext = strtok_r (NULL, ";", &plast); if (pnext && sscanf (pnext, "q=%f", &q) == 1) { type_q = q; } } type_q = 1.0; } next = strtok_r (NULL, ",", &last); } if (q > 0.0) return q; else if (type_q > 0.0) return type_q; else return all_q; } #ifdef __PROTOTYPE static int get_cgi_mode (request_rec * r, table * cgi_table) { char * cgi_mode; cgi_mode = (char *)table_get (cgi_table, "mode"); if (cgi_mode == NULL) return MOD_ANNODEX_TYPE_UNKNOWN; #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "cgi mode %s", cgi_mode); #endif if (!strcmp (cgi_mode, "cmml")) return MOD_ANNODEX_TYPE_CMML; else if (!strcmp (cgi_mode, "media")) return MOD_ANNODEX_TYPE_MEDIA; else return MOD_ANNODEX_TYPE_UNKNOWN; } #endif /* __PROTOTYPE */ static int prefer_cmml (request_rec * r) { float qc, qa; qc = get_accept_quality (r, CMML_MIME_TYPE); qa = get_accept_quality (r, ANX_MIME_TYPE); #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Accept CMML %f, Accept ANX %f", qc, qa); #endif return (qc > qa); } /** * Parse the first time range in a string; multiple time ranges may be * separated by commas. * @param s The string to parse. * @param range_start The location to store the start of the range. * @param range_end The location to store the end of the range. * @return a pointer to the start of the next time range, or NULL if * no further ranges are specified in s. */ static char * ma_parse_time (char * s, double * range_start, double * range_end) { char * sep, * end; sep = strchr (s, '/'); end = strchr (s, ','); if (end != NULL) { /* If we found the separator for the following range, then * ignore it while processing this range. */ if (sep && end < sep) sep = NULL; /* Terminate the range for string processing */ *end++ = '\0'; } if (sep == NULL) { *range_start = anx_parse_time (s); *range_end = -1.0; } else { /* Terminate the range start */ *sep++ = '\0'; *range_start = anx_parse_time (s); *range_end = anx_parse_time (sep); } return end; } static double ma_anxenc_clip_time (char * filename, char * content_type, char * id) { ANNODEX * anx; int ret; unsigned char buf[1024]; long n = 1024; double clip_offset; anx = anx_new (ANX_WRITE); ret = anx_write_import (anx, filename, NULL, content_type, 0.0, -1.0, 0); /* Only spin through the file if the requested clip has not yet been * inserted; avoid scanning media if 'filename' is a cmml file. */ if ((clip_offset = anx_get_clip_time_by_id (anx, id)) == -1.0) { while ((n = anx_write_output (anx, buf, 1024)) > 0); if ((clip_offset = anx_get_clip_time_by_id (anx, id)) == -1.0) { clip_offset = 0.0; } } if (anx_close (anx) != NULL) { } return clip_offset; } static int ma_anxenc (request_rec * r, char * filename, char * mime_type, table * cgi_table) { ANNODEX * anx; int ret; char * val, * id = NULL; double seek_offset = 0.0, seek_end = -1.0; char buf[1024]; long n = 1024; long ntotal = 0; double header_value; char header_buf[64]; char olddir[PATH_MAX], * newdir, * sep_pos; anx = anx_new (ANX_WRITE); /*anx_init_importers (ANX_MIME_TYPE);*/ anx_init_importers ("*/*"); /* Retrieve the current working directory */ getcwd (olddir, PATH_MAX); /* Chdir to the directory of the requested file */ newdir = ap_pstrdup (r->pool, filename); sep_pos = strrchr (newdir, '/'); if (sep_pos) *sep_pos = '\0'; chdir (newdir); /* Get the start/end times or id */ val = (char *)table_get (cgi_table, "t"); id = (char *)table_get (cgi_table, "id"); if (val) { ma_parse_time (val, &seek_offset, &seek_end); } else if (id) { seek_offset = ma_anxenc_clip_time (filename, mime_type, id); } #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "ma_anxenc: t=%s id=%s (%f/%f)", val, id, seek_offset, seek_end); #endif anx_set_presentation_time (anx, seek_offset); anx_set_basetime (anx, 0.0); ret = anx_write_import (anx, filename, NULL, mime_type, seek_offset, seek_end, 0); #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "ma_anxenc: anx_write_import returned %d", ret); #endif if ((header_value = anx_get_duration (anx)) != -1.0) { snprintf (header_buf, 64, "%ld", (long)header_value); #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "ma_anxenc: duration %s", header_buf); #endif table_set (r->headers_out, (const char *)"X-Content-Duration", header_buf); } if ((header_value = anx_get_bitrate (anx)) != -1.0) { snprintf (header_buf, 64, "%ld", (long)header_value); table_set (r->headers_out, (const char *)"X-Content-Bitrate-Average", header_buf); } ap_send_http_header(r); while ((n = anx_write_output (anx, buf, 1024)) > 0) { ntotal += n; ap_rwrite (buf, n, r); } #ifdef DEBUG if (n == -1) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "ma_anxenc: %s", anx_strerror (anx)); } #endif #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "ma_anxenc: done, with n=%ld (ntotal=%ld)", n, ntotal); #endif if (anx_close (anx) != NULL) { return -1; } /* Return to the previously current working directory */ chdir (olddir); return 0; } /* ma_anxrip callbacks */ struct ma_anxrip_data_t { request_rec * r; AnxClip * prev_clip; double prev_start_time; }; static int read_head (ANNODEX * anx, const AnxHead * head, void * user_data) { struct ma_anxrip_data_t * mad = (struct ma_anxrip_data_t *)user_data; request_rec * r = mad->r; char buf[MAX_CLIP_LEN]; anx_head_snprint (buf, MAX_CLIP_LEN, (AnxHead *)head); ap_rputs (buf, r); ap_rputc ('\n', r); return ANX_CONTINUE; } static int read_clip (ANNODEX * anx, const AnxClip * clip, void * user_data) { struct ma_anxrip_data_t * mad = (struct ma_anxrip_data_t *)user_data; request_rec * r = mad->r; char buf[MAX_CLIP_LEN]; double t; t = anx_tell_time (anx); /* fprintf (outfile, "%.3f (%ld)\t", t, anx_tell (anx));*/ if (mad->prev_clip != NULL) { anx_clip_snprint (buf, MAX_CLIP_LEN, mad->prev_clip, mad->prev_start_time, t); ap_rputs (buf, r); ap_rputc ('\n', r); anx_clip_free (mad->prev_clip); } mad->prev_clip = anx_clip_clone ((AnxClip *)clip); mad->prev_start_time = t; return ANX_CONTINUE; } static int ma_anxrip (request_rec * r, char * filename) { ANNODEX * anx; char buf[MAX_CLIP_LEN]; size_t n = MAX_CLIP_LEN; #if 0 AnxList * l; AnxTrack * s; #endif double timebase; struct ma_anxrip_data_t mad; ap_send_http_header(r); mad.r = r; mad.prev_clip = NULL; anx = anx_open (filename, ANX_READ); anx_set_read_head_callback (anx, read_head, &mad); anx_set_read_clip_callback (anx, read_clip, &mad); ap_rprintf (r, CMML_PREAMBLE); ap_rprintf (r, "\n"); ap_rprintf (r, "\n"); /* XXX: and UTC */ #if 0 for (l = anx_get_track_list (anx); l; l = l->next) { s = (AnxTrack *)l->data; if (!(s->mime_type && !strncmp (s->mime_type, CMML_MIME_TYPE, 11))) { ap_rprintf (r, "id) ap_rprintf (r, " id=\"%s\"", s->id); if (s->mime_type) ap_rprintf (r, " contenttype=\"%s\"", s->mime_type); /* if (s->granule_rate_n != 0 && s->granule_rate_d != 0) ap_rprintf (r, " granulerate=\"%ld/%ld\"", (long)s->granule_rate_n, (long)s->granule_rate_d); */ ap_rprintf (r, "/>\n"); } } #endif ap_rprintf (r, "\n"); while ((n = anx_read (anx, 1024)) > 0); if (mad.prev_clip != NULL) { anx_clip_snprint (buf, MAX_CLIP_LEN, mad.prev_clip, mad.prev_start_time, anx_tell_time (anx)); ap_rputs (buf, r); ap_rputc ('\n', r); anx_clip_free (mad.prev_clip); } ap_rprintf (r, "\n"); if (anx_close (anx) != NULL) { return -1; } return 0; } #ifdef __PROTOTYPE /* ma_anxrip_media callbacks */ static int read_m_stream (ANNODEX * anx, double timebase, char * utc, void * user_data) { return ANX_CONTINUE; } static int read_m_track (ANNODEX * anx, long serialno, char * id, char * mime_type, anx_int64_t granule_rate_n, anx_int64_t granule_rate_d, int nr_header_packets, void * user_data) { request_rec * r = (request_rec *)user_data; if (!(mime_type && !strncmp (mime_type, "text/x-annodex", 14))) { r->content_type = mime_type; #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Rip media: mime type %s", r->content_type); #endif } return ANX_CONTINUE; } static int read_media (ANNODEX * anx, unsigned char * data, long len, long serialno, anx_int64_t granulepos, void * user_data) { request_rec * r = (request_rec *)user_data; size_t remaining = (size_t)len, n; #if 0 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "%f\t%f", anx_tell_time (anx), s_seek_offset); if (anx_tell_time (anx) >= s_seek_offset) #endif while (remaining > 0) { n = ap_rwrite (data, remaining, r); remaining -= n; } return ANX_CONTINUE; } static int read_media_1 (ANNODEX * anx, unsigned char * data, long len, long serialno, anx_int64_t granulepos, void * user_data) { request_rec * r = (request_rec *)user_data; r->content_type = anx_track_get_content_type (anx, serialno); ap_send_http_header(r); if (r->header_only) { /* XXX: Should be smarter , ie. drop out properly :) */ anx_set_read_raw_callback (anx, NULL, user_data); return ANX_STOP_OK; } read_media (anx, data, len, serialno, granulepos, user_data); anx_set_read_raw_callback (anx, read_media, user_data); return ANX_CONTINUE; } static int ma_anxrip_media (request_rec * r, char * filename, table * cgi_table) { ANNODEX * anx; double seek_offset = 0.0; char * val; long n; ap_send_http_header(r); val = (char *)table_get (cgi_table, "t"); if (val) { seek_offset = anx_parse_time (val); } anx = anx_open (filename, ANX_READ); anx_set_read_stream_callback (anx, read_m_stream, r); anx_set_read_track_callback (anx, read_m_track, r); anx_set_read_raw_callback (anx, read_media_1, r); if (seek_offset > 0.0) anx_seek_time (anx, seek_offset, ANX_SEEK_SET); /*s_seek_offset = seek_offset;*/ while ((n = anx_read (anx, 1024)) > 0); if (anx_close (anx) != NULL) { return -1; } return 0; } #endif #ifdef UNUSED static int ma_send (request_rec * r, char * filename) { FILE * f; char buf[1024]; size_t n = 1024; ap_send_http_header(r); if (!(f = ap_pfopen (r->pool, filename, "r"))) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "Permissions deny server access: %s", filename); return HTTP_FORBIDDEN; } #ifdef DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "ma_send: %s", filename); #endif while ((n = fread ((void *)buf, 1, 1024, f)) > 0) { ap_rwrite (buf, n, r); } ap_pfclose (r->pool, f); return 0; } #endif static int read_cmml_stream (CMML * cmml, const CMML_Stream * stream, void * user_data) { request_rec * r = (request_rec *)user_data; CMML_Preamble * preamble; static char buf[1024]; int n; preamble = cmml_get_preamble (cmml); n = cmml_preamble_snprint (buf, 1024, preamble); if (n > 0) { ap_rwrite (buf, n, r); } return 0; } static int read_cmml_head (CMML * cmml, const CMML_Head * head, void * user_data) { request_rec * r = (request_rec *)user_data; static char buf[MAX_CLIP_LEN]; int n; n = cmml_head_pretty_snprint (buf, MAX_CLIP_LEN, (CMML_Head *)head); if (n > 0) { ap_rwrite (buf, n, r); } return 0; } static int read_cmml_clip (CMML * cmml, const CMML_Clip * clip, void * user_data) { request_rec * r = (request_rec *)user_data; static char buf[MAX_CLIP_LEN]; int n; n = cmml_clip_pretty_snprint (buf, MAX_CLIP_LEN, (CMML_Clip *)clip); if (n > 0) { ap_rwrite (buf, n, r); } return 0; } static int ma_send_cmml (request_rec * r, char * filename) { CMML * doc; size_t nread = 0; long n; static char * cmml_tail = "\n"; ap_send_http_header(r); doc = cmml_open (filename); cmml_set_read_callbacks (doc, read_cmml_stream, read_cmml_head, read_cmml_clip, r); while ((n = cmml_read (doc, 1024)) > 0) nread += n; if (n < 0) { /* ERROR */ } n = strlen (cmml_tail); if (n > 0) { ap_rwrite (cmml_tail, n, r); } cmml_destroy (doc); return 0; } static char * ma_extsub (request_rec * r, char * filename, char * oldext, int oldext_len, char * newext, int newext_len) { char * extpos; char * srcfile; int filename_len; int diff; /* how many bytes longer the new extension is */ char * ret; extpos = strrchr (r->filename, '.'); if (extpos == NULL) return NULL; if (strncmp (++extpos, oldext, oldext_len)) return NULL; /* s/.oldext/.newext/ */ diff = newext_len - oldext_len; filename_len = strlen (filename); srcfile = palloc (r->pool, filename_len + diff + 1 /* '\0' */); strncpy (srcfile, filename, filename_len-oldext_len+1); strncpy (srcfile+filename_len-oldext_len, newext, newext_len+1); ret = r->path_info ? pstrcat (r->pool, srcfile, r->path_info, NULL) : srcfile; return ret; } /* The annodex content handler */ static int annodex_handler(request_rec *r) { uri_components * uri; char * filename; char * srcpath = NULL, * cmmlpath = NULL; int input_type, output_type; table * cgi_table; uri = &(r->parsed_uri); /* usually filename is request filename */ filename = r->filename; /* but we still need to determine the output type */ output_type = MOD_ANNODEX_TYPE_UNKNOWN; if (strcmp(r->handler, "annodex")) { return DECLINED; } if (r->method_number == M_OPTIONS) { r->allowed = (1 << M_GET); return DECLINED; } if (r->method_number != M_GET) { return HTTP_METHOD_NOT_ALLOWED; } /* check if file exists */ if (r->finfo.st_mode == 0) { struct stat statbuf; if ((srcpath = ma_extsub (r, r->filename, "anx", 3, "cmml", 4))) { if (stat (srcpath, &statbuf) < 0) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Requested file does not exist, nor does CMML %s", srcpath); return HTTP_NOT_FOUND; } input_type = MOD_ANNODEX_TYPE_CMML; filename = srcpath; } else if ((srcpath = ma_extsub (r, r->filename, "cmml", 4, "anx", 3))) { if (stat (srcpath, &statbuf) < 0) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Requested file %s does not exist, nor does ANX %s", r->filename, srcpath); return HTTP_NOT_FOUND; } input_type = MOD_ANNODEX_TYPE_ANX; output_type = MOD_ANNODEX_TYPE_CMML; filename = srcpath; } else { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Requested file does not exist: %s", (r->path_info ? pstrcat(r->pool, r->filename, r->path_info, NULL) : r->filename)); return HTTP_NOT_FOUND; } } else { struct stat statbuf; if ((cmmlpath = ma_extsub (r, r->filename, "anx", 3, "cmml", 4))) { if (stat (cmmlpath, &statbuf) < 0) { cmmlpath = NULL; } else { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Using stored CMML file %s", cmmlpath); } } input_type = MOD_ANNODEX_TYPE_ANX; } cgi_table = make_cgi_table (r, uri->query); #ifdef __PROTOTYPE if (output_type == MOD_ANNODEX_TYPE_UNKNOWN) output_type = get_cgi_mode (r, cgi_table); #endif if (output_type == MOD_ANNODEX_TYPE_UNKNOWN) { if (prefer_cmml (r)) { output_type = MOD_ANNODEX_TYPE_CMML; } else { output_type = MOD_ANNODEX_TYPE_ANX; } } if (output_type == MOD_ANNODEX_TYPE_CMML) { r->content_type = CMML_MIME_TYPE; } else { r->content_type = ANX_MIME_TYPE; } table_set (r->headers_out, (const char *)"X-Accept-TimeURI", ANX_MIME_TYPE); #ifdef BLAHBLIMBLAM__PROTOTYPE if (!(input_type == MOD_ANNODEX_TYPE_ANX && output_type == MOD_ANNODEX_TYPE_MEDIA)) { ap_send_http_header(r); if (r->header_only) { return OK; } } #else if (r->header_only) { ap_send_http_header(r); return OK; } #endif if (input_type == MOD_ANNODEX_TYPE_ANX && output_type == MOD_ANNODEX_TYPE_ANX) { /* anx import anx */ ma_anxenc (r, r->filename, ANX_MIME_TYPE, cgi_table); } else if (input_type == MOD_ANNODEX_TYPE_CMML && output_type == MOD_ANNODEX_TYPE_ANX) { /* anx import cmml */ ma_anxenc (r, srcpath, CMML_MIME_TYPE, cgi_table); } else if (input_type == MOD_ANNODEX_TYPE_ANX && output_type == MOD_ANNODEX_TYPE_CMML) { if (cmmlpath != NULL) { /* push out cmml */ ma_send_cmml (r, cmmlpath); } else { /* anx rip cmml */ ma_anxrip (r, filename); } } else if (input_type == MOD_ANNODEX_TYPE_CMML && output_type == MOD_ANNODEX_TYPE_CMML) { /* push out cmml */ ma_send_cmml (r, srcpath); #ifdef __PROTOTYPE } else if (input_type == MOD_ANNODEX_TYPE_ANX && output_type == MOD_ANNODEX_TYPE_MEDIA) { r->content_type = "video/mpeg"; ma_anxrip_media (r, r->filename, cgi_table); #endif } return OK; } /* Dispatch list of content handlers */ static const handler_rec annodex_handlers[] = { { "annodex", annodex_handler }, { NULL, NULL } }; /* Dispatch list for API hooks */ module MODULE_VAR_EXPORT annodex_module = { STANDARD_MODULE_STUFF, NULL, /* module initializer */ NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ annodex_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 */ NULL, /* [#7] pre-run fixups */ NULL, /* [#9] log a transaction */ NULL, /* [#2] header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* [#0] post read-request */ #ifdef EAPI ,NULL, /* EAPI: add_module */ NULL, /* EAPI: remove_module */ NULL, /* EAPI: rewrite_command */ NULL /* EAPI: new_connection */ #endif };