components/apache2/mod_sed/mod_sed.c
changeset 278 77b380ba9d84
equal deleted inserted replaced
277:12ebd29ad46c 278:77b380ba9d84
       
     1 /*
       
     2  * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
       
     3  * Use is subject to license terms.
       
     4  *
       
     5  * Licensed under the Apache License, Version 2.0 (the "License");
       
     6  * you may not use this file except in compliance with the License.
       
     7  * You may obtain a copy of the License at
       
     8  *  http://www.apache.org/licenses/LICENSE-2.0. 
       
     9  * 
       
    10  * Unless required by applicable law or agreed to in writing, software 
       
    11  * distributed under the License is distributed on an "AS IS" BASIS, 
       
    12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
       
    13  * or implied. 
       
    14  * See the License for the specific language governing permissions and
       
    15  * limitations under the License. 
       
    16  */
       
    17 
       
    18 #include "httpd.h"
       
    19 #include "http_config.h"
       
    20 #include "http_log.h"
       
    21 #include "apr_strings.h"
       
    22 #include "apr_general.h"
       
    23 #include "util_filter.h"
       
    24 #include "apr_buckets.h"
       
    25 #include "http_request.h"
       
    26 #include "libsed.h"
       
    27 
       
    28 static const char *sed_filter_name = "Sed";
       
    29 #define MODSED_OUTBUF_SIZE 8000
       
    30 #define MAX_TRANSIENT_BUCKETS 50
       
    31 
       
    32 typedef struct sed_expr_config
       
    33 {
       
    34     sed_commands_t *sed_cmds;
       
    35     const char *last_error;
       
    36 } sed_expr_config;
       
    37 
       
    38 typedef struct sed_config
       
    39 {
       
    40     sed_expr_config output;
       
    41     sed_expr_config input;
       
    42 } sed_config;
       
    43 
       
    44 /* Context for filter invocation for single HTTP request */
       
    45 typedef struct sed_filter_ctxt
       
    46 {
       
    47     sed_eval_t eval;
       
    48     ap_filter_t *f;
       
    49     request_rec *r;
       
    50     apr_bucket_brigade *bb;
       
    51     char *outbuf;
       
    52     char *curoutbuf;
       
    53     int bufsize;
       
    54     apr_pool_t *tpool;
       
    55     int numbuckets;
       
    56 } sed_filter_ctxt;
       
    57 
       
    58 module AP_MODULE_DECLARE_DATA sed_module;
       
    59 
       
    60 /* This function will be call back from libsed functions if there is any error
       
    61  * happend during execution of sed scripts
       
    62  */
       
    63 static apr_status_t log_sed_errf(void *data, const char *error)
       
    64 {
       
    65     request_rec *r = (request_rec *) data;
       
    66     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error);
       
    67     return APR_SUCCESS;
       
    68 }
       
    69 
       
    70 /* This function will be call back from libsed functions if there is any
       
    71  * compilation error.
       
    72  */
       
    73 static apr_status_t sed_compile_errf(void *data, const char *error)
       
    74 {
       
    75     sed_expr_config *sed_cfg = (sed_expr_config *) data;
       
    76     sed_cfg->last_error = error;
       
    77     return APR_SUCCESS;
       
    78 }
       
    79 
       
    80 /* clear the temporary pool (used for transient buckets)
       
    81  */
       
    82 static void clear_ctxpool(sed_filter_ctxt* ctx)
       
    83 {
       
    84     apr_pool_clear(ctx->tpool);
       
    85     ctx->outbuf = NULL;
       
    86     ctx->curoutbuf = NULL;
       
    87     ctx->numbuckets = 0;
       
    88 }
       
    89 
       
    90 /* alloc_outbuf
       
    91  * allocate output buffer
       
    92  */
       
    93 static void alloc_outbuf(sed_filter_ctxt* ctx)
       
    94 {
       
    95     ctx->outbuf = apr_palloc(ctx->tpool, ctx->bufsize + 1);
       
    96     ctx->curoutbuf = ctx->outbuf;
       
    97 }
       
    98 
       
    99 /* append_bucket
       
   100  * Allocate a new bucket from buf and sz and append to ctx->bb
       
   101  */
       
   102 static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz)
       
   103 {
       
   104     apr_status_t status = APR_SUCCESS;
       
   105     apr_bucket *b;
       
   106     if (ctx->tpool == ctx->r->pool) {
       
   107         /* We are not using transient bucket */
       
   108         b = apr_bucket_pool_create(buf, sz, ctx->r->pool,
       
   109                                    ctx->r->connection->bucket_alloc);
       
   110         APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
       
   111     }
       
   112     else {
       
   113         /* We are using transient bucket */
       
   114         b = apr_bucket_transient_create(buf, sz,
       
   115                                         ctx->r->connection->bucket_alloc);
       
   116         APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
       
   117         ctx->numbuckets++;
       
   118         if (ctx->numbuckets >= MAX_TRANSIENT_BUCKETS) {
       
   119             b = apr_bucket_flush_create(ctx->r->connection->bucket_alloc);
       
   120             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
       
   121             status = ap_pass_brigade(ctx->f->next, ctx->bb);
       
   122             apr_brigade_cleanup(ctx->bb);
       
   123             clear_ctxpool(ctx);
       
   124         }
       
   125     }
       
   126     return status;
       
   127 }
       
   128 
       
   129 /*
       
   130  * flush_output_buffer
       
   131  * Flush the  output data (stored in ctx->outbuf)
       
   132  */
       
   133 static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx)
       
   134 {
       
   135     int size = ctx->curoutbuf - ctx->outbuf;
       
   136     char *out;
       
   137     apr_status_t status = APR_SUCCESS;
       
   138     if ((ctx->outbuf == NULL) || (size <=0))
       
   139         return status;
       
   140     out = apr_palloc(ctx->tpool, size);
       
   141     memcpy(out, ctx->outbuf, size);
       
   142     status = append_bucket(ctx, out, size);
       
   143     ctx->curoutbuf = ctx->outbuf;
       
   144     return status;
       
   145 }
       
   146 
       
   147 /* This is a call back function. When libsed wants to generate the output,
       
   148  * this function will be invoked.
       
   149  */
       
   150 static apr_status_t sed_write_output(void *dummy, char *buf, int sz)
       
   151 {
       
   152     /* dummy is basically filter context. Context is passed during invocation
       
   153      * of sed_eval_buffer
       
   154      */
       
   155     int remainbytes = 0;
       
   156     apr_status_t status = APR_SUCCESS;
       
   157     sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy;
       
   158     if (ctx->outbuf == NULL) {
       
   159         alloc_outbuf(ctx);
       
   160     }
       
   161     remainbytes = ctx->bufsize - (ctx->curoutbuf - ctx->outbuf);
       
   162     if (sz >= remainbytes) {
       
   163         if (remainbytes > 0) {
       
   164             memcpy(ctx->curoutbuf, buf, remainbytes);
       
   165             buf += remainbytes;
       
   166             sz -= remainbytes;
       
   167             ctx->curoutbuf += remainbytes;
       
   168         }
       
   169         /* buffer is now full */
       
   170         status = append_bucket(ctx, ctx->outbuf, ctx->bufsize);
       
   171         /* old buffer is now used so allocate new buffer */
       
   172         alloc_outbuf(ctx);
       
   173         /* if size is bigger than the allocated buffer directly add to output
       
   174          * brigade */
       
   175         if ((status == APR_SUCCESS) && (sz >= ctx->bufsize)) {
       
   176             char* newbuf = apr_palloc(ctx->tpool, sz);
       
   177             memcpy(newbuf, buf, sz);
       
   178             status = append_bucket(ctx, newbuf, sz);
       
   179             /* pool might get clear after append_bucket */
       
   180             if (ctx->outbuf == NULL) {
       
   181                 alloc_outbuf(ctx);
       
   182             }
       
   183         }
       
   184         else {
       
   185             memcpy(ctx->curoutbuf, buf, sz);
       
   186             ctx->curoutbuf += sz;
       
   187         }
       
   188     }
       
   189     else {
       
   190         memcpy(ctx->curoutbuf, buf, sz);
       
   191         ctx->curoutbuf += sz;
       
   192     }
       
   193     return status;
       
   194 }
       
   195 
       
   196 /* Compile a sed expression. Compiled context is saved in sed_cfg->sed_cmds.
       
   197  * Memory required for compilation context is allocated from cmd->pool.
       
   198  */
       
   199 static apr_status_t compile_sed_expr(sed_expr_config *sed_cfg,
       
   200                                      cmd_parms *cmd,
       
   201                                      const char *expr)
       
   202 {
       
   203     apr_status_t status = APR_SUCCESS;
       
   204 
       
   205     if (!sed_cfg->sed_cmds) {
       
   206         sed_commands_t *sed_cmds;
       
   207         sed_cmds = apr_pcalloc(cmd->pool, sizeof(sed_commands_t));
       
   208         status = sed_init_commands(sed_cmds, sed_compile_errf, sed_cfg,
       
   209                                    cmd->pool);
       
   210         if (status != APR_SUCCESS) {
       
   211             sed_destroy_commands(sed_cmds);
       
   212             return status;
       
   213         }
       
   214         sed_cfg->sed_cmds = sed_cmds;
       
   215     }
       
   216     status = sed_compile_string(sed_cfg->sed_cmds, expr);
       
   217     if (status != APR_SUCCESS) {
       
   218         sed_destroy_commands(sed_cfg->sed_cmds);
       
   219         sed_cfg->sed_cmds = NULL;
       
   220     }
       
   221     return status;
       
   222 }
       
   223 
       
   224 /* sed eval cleanup function */
       
   225 static apr_status_t sed_eval_cleanup(void *data)
       
   226 {
       
   227     sed_eval_t *eval = (sed_eval_t *) data;
       
   228     sed_destroy_eval(eval);
       
   229     return APR_SUCCESS;
       
   230 }
       
   231 
       
   232 /* Initialize sed filter context. If successful then context is set in f->ctx
       
   233  */
       
   234 static apr_status_t init_context(ap_filter_t *f, sed_expr_config *sed_cfg, int usetpool)
       
   235 {
       
   236     apr_status_t status;
       
   237     sed_filter_ctxt* ctx;
       
   238     request_rec *r = f->r;
       
   239     /* Create the context. Call sed_init_eval. libsed will generated
       
   240      * output by calling sed_write_output and generates any error by
       
   241      * invoking log_sed_errf.
       
   242      */
       
   243     ctx = apr_pcalloc(r->pool, sizeof(sed_filter_ctxt));
       
   244     ctx->r = r;
       
   245     ctx->bb = NULL;
       
   246     ctx->numbuckets = 0;
       
   247     ctx->f = f;
       
   248     status = sed_init_eval(&ctx->eval, sed_cfg->sed_cmds, log_sed_errf,
       
   249                            r, &sed_write_output, r->pool);
       
   250     if (status != APR_SUCCESS) {
       
   251         return status;
       
   252     }
       
   253     apr_pool_cleanup_register(r->pool, &ctx->eval, sed_eval_cleanup,
       
   254                               apr_pool_cleanup_null);
       
   255     ctx->bufsize = MODSED_OUTBUF_SIZE;
       
   256     if (usetpool) {
       
   257         apr_pool_create(&(ctx->tpool), r->pool);
       
   258     }
       
   259     else {
       
   260         ctx->tpool = r->pool;
       
   261     }
       
   262     alloc_outbuf(ctx);
       
   263     f->ctx = ctx;
       
   264     return APR_SUCCESS;
       
   265 }
       
   266 
       
   267 /* Entry function for Sed output filter */
       
   268 static apr_status_t sed_response_filter(ap_filter_t *f,
       
   269                                         apr_bucket_brigade *bb)
       
   270 {
       
   271     apr_bucket *b;
       
   272     apr_status_t status;
       
   273     sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
       
   274                                            &sed_module);
       
   275     sed_filter_ctxt *ctx = f->ctx;
       
   276     sed_expr_config *sed_cfg = &cfg->output;
       
   277 
       
   278     if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
       
   279         /* No sed expressions */
       
   280         ap_remove_output_filter(f);
       
   281         return ap_pass_brigade(f->next, bb);
       
   282     }
       
   283 
       
   284     if (ctx == NULL) {
       
   285 
       
   286         if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
       
   287             /* no need to run sed filter for Head requests */
       
   288             ap_remove_output_filter(f);
       
   289             return ap_pass_brigade(f->next, bb);
       
   290         }
       
   291 
       
   292         status = init_context(f, sed_cfg, 1);
       
   293         if (status != APR_SUCCESS)
       
   294              return status;
       
   295         ctx = f->ctx;
       
   296         apr_table_unset(f->r->headers_out, "Content-Length");
       
   297     }
       
   298 
       
   299     ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
       
   300 
       
   301     /* Here is the main logic. Iterate through all the buckets, read the
       
   302      * content of the bucket, call sed_eval_buffer on the data.
       
   303      * sed_eval_buffer will read the data line by line, run filters on each
       
   304      * line. sed_eval_buffer will generates the output by calling
       
   305      * sed_write_output which will add the output to ctx->bb. At the end of
       
   306      * the loop, ctx->bb is passed to the next filter in chain. At the end of
       
   307      * the data, if new line is not found then sed_eval_buffer will store the
       
   308      * data in it's own buffer.
       
   309      *
       
   310      * Once eos bucket is found then sed_finalize_eval will flush the rest of
       
   311      * the data. If there is no new line in last line of data, new line is
       
   312      * appended (that is a solaris sed behavior). libsed's internal memory for
       
   313      * evaluation is allocated on request's pool so it will be cleared once
       
   314      * request is over.
       
   315      *
       
   316      * If flush bucket is found then append the the flush bucket to ctx->bb
       
   317      * and pass it to next filter. There may be some data which will still be
       
   318      * in sed's internal buffer which can't be flushed until new line
       
   319      * character is arrived.
       
   320      */
       
   321     for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);) {
       
   322         const char *buf = NULL;
       
   323         apr_size_t bytes = 0;
       
   324         if (APR_BUCKET_IS_EOS(b)) {
       
   325             apr_bucket *b1 = APR_BUCKET_NEXT(b);
       
   326             /* Now clean up the internal sed buffer */
       
   327             sed_finalize_eval(&ctx->eval, ctx);
       
   328             status = flush_output_buffer(ctx);
       
   329             if (status != APR_SUCCESS) {
       
   330                 clear_ctxpool(ctx);
       
   331                 return status;
       
   332             }
       
   333             APR_BUCKET_REMOVE(b);
       
   334             /* Insert the eos bucket to ctx->bb brigade */
       
   335             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
       
   336             b = b1;
       
   337         }
       
   338         else if (APR_BUCKET_IS_FLUSH(b)) {
       
   339             apr_bucket *b1 = APR_BUCKET_NEXT(b);
       
   340             APR_BUCKET_REMOVE(b);
       
   341             status = flush_output_buffer(ctx);
       
   342             if (status != APR_SUCCESS) {
       
   343                 clear_ctxpool(ctx);
       
   344                 return status;
       
   345             }
       
   346             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
       
   347             b = b1;
       
   348         }
       
   349         else if (APR_BUCKET_IS_METADATA(b)) {
       
   350             b = APR_BUCKET_NEXT(b);
       
   351         }
       
   352         else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
       
   353                  == APR_SUCCESS) {
       
   354             apr_bucket *b1 = APR_BUCKET_NEXT(b);
       
   355             status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
       
   356             if (status != APR_SUCCESS) {
       
   357                 clear_ctxpool(ctx);
       
   358                 return status;
       
   359             }
       
   360             APR_BUCKET_REMOVE(b);
       
   361             apr_bucket_delete(b);
       
   362             b = b1;
       
   363         }
       
   364         else {
       
   365             apr_bucket *b1 = APR_BUCKET_NEXT(b);
       
   366             APR_BUCKET_REMOVE(b);
       
   367             b = b1;
       
   368         }
       
   369     }
       
   370     apr_brigade_cleanup(bb);
       
   371     status = flush_output_buffer(ctx);
       
   372     if (status != APR_SUCCESS) {
       
   373         clear_ctxpool(ctx);
       
   374         return status;
       
   375     }
       
   376     if (!APR_BRIGADE_EMPTY(ctx->bb)) {
       
   377         status = ap_pass_brigade(f->next, ctx->bb);
       
   378         apr_brigade_cleanup(ctx->bb);
       
   379     }
       
   380     clear_ctxpool(ctx);
       
   381     return status;
       
   382 }
       
   383 
       
   384 /* Entry function for Sed input filter */
       
   385 static apr_status_t sed_request_filter(ap_filter_t *f,
       
   386                                        apr_bucket_brigade *bb,
       
   387                                        ap_input_mode_t mode,
       
   388                                        apr_read_type_e block,
       
   389                                        apr_off_t readbytes)
       
   390 {
       
   391     sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
       
   392                                            &sed_module);
       
   393     sed_filter_ctxt *ctx = f->ctx;
       
   394     apr_status_t status;
       
   395     sed_expr_config *sed_cfg = &cfg->input;
       
   396 
       
   397     if (mode != AP_MODE_READBYTES) {
       
   398         return ap_get_brigade(f->next, bb, mode, block, readbytes);
       
   399     }
       
   400 
       
   401     if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
       
   402         /* No sed expression */
       
   403         return ap_get_brigade(f->next, bb, mode, block, readbytes);
       
   404     }
       
   405 
       
   406     if (!ctx) {
       
   407         if (!ap_is_initial_req(f->r)) {
       
   408             ap_remove_input_filter(f);
       
   409             /* XXX : Should we filter the sub requests too */
       
   410             return ap_get_brigade(f->next, bb, mode, block, readbytes);
       
   411         }
       
   412         status = init_context(f, sed_cfg, 0);
       
   413         if (status != APR_SUCCESS)
       
   414              return status;
       
   415         ctx = f->ctx;
       
   416         ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
       
   417     }
       
   418 
       
   419     /* Here is the logic :
       
   420      * Read the readbytes data from next level fiter into bbinp. Loop through
       
   421      * the buckets in bbinp and read the data from buckets and invoke
       
   422      * sed_eval_buffer on the data. libsed will generate it's output using
       
   423      * sed_write_output which will add data in ctx->bb. Do it until it have
       
   424      * atleast one bucket bucket in ctx->bb. At the end of data eos bucket
       
   425      * should be there.
       
   426      *
       
   427      * Once eos bucket is seen, then invoke sed_finalize_eval to clear the
       
   428      * output. If the last byte of data is not a new line character then sed
       
   429      * will add a new line to the data that is default sed behaviour. Note
       
   430      * that using this filter with POST data, caller may not expect this
       
   431      * behaviour.
       
   432      *
       
   433      * If next level fiter generate the flush bucket, we can't do much about
       
   434      * it. If we want to return the flush bucket in brigade bb (to the caller)
       
   435      * the question is where to add it?
       
   436      */
       
   437     while (APR_BRIGADE_EMPTY(ctx->bb)) {
       
   438         apr_bucket_brigade *bbinp;
       
   439         apr_bucket *b;
       
   440 
       
   441         /* read the bytes from next level filter */
       
   442         bbinp = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
       
   443         status = ap_get_brigade(f->next, bbinp, mode, block, readbytes);
       
   444         if (status != APR_SUCCESS) {
       
   445             return status;
       
   446         }
       
   447         for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp);
       
   448              b = APR_BUCKET_NEXT(b)) {
       
   449             const char *buf = NULL;
       
   450             apr_size_t bytes;
       
   451 
       
   452             if (APR_BUCKET_IS_EOS(b)) {
       
   453                 /* eos bucket. Clear the internal sed buffers */
       
   454                 sed_finalize_eval(&ctx->eval, ctx);
       
   455                 flush_output_buffer(ctx);
       
   456                 APR_BUCKET_REMOVE(b);
       
   457                 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
       
   458                 break;
       
   459             }
       
   460             else if (APR_BUCKET_IS_FLUSH(b)) {
       
   461                 /* What should we do with flush bucket */
       
   462                 continue;
       
   463             }
       
   464             if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
       
   465                      == APR_SUCCESS) {
       
   466                 status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
       
   467                 if (status != APR_SUCCESS)
       
   468                     return status;
       
   469                 flush_output_buffer(ctx);
       
   470             }
       
   471         }
       
   472         apr_brigade_cleanup(bbinp);
       
   473         apr_brigade_destroy(bbinp);
       
   474     }
       
   475 
       
   476     if (!APR_BRIGADE_EMPTY(ctx->bb)) {
       
   477         apr_bucket_brigade *newbb = NULL;
       
   478         apr_bucket *b = NULL;
       
   479 
       
   480         /* This may return APR_INCOMPLETE which should be fine */
       
   481         apr_brigade_partition(ctx->bb, readbytes, &b);
       
   482 
       
   483         newbb = apr_brigade_split(ctx->bb, b);
       
   484         APR_BRIGADE_CONCAT(bb, ctx->bb);
       
   485         APR_BRIGADE_CONCAT(ctx->bb, newbb);
       
   486     }
       
   487     return APR_SUCCESS;
       
   488 }
       
   489 
       
   490 static const char *sed_add_expr(cmd_parms *cmd, void *cfg, const char *arg)
       
   491 {
       
   492     int offset = (int) (long) cmd->info;
       
   493     sed_expr_config *sed_cfg = 
       
   494                 (sed_expr_config *) (((char *) cfg) + offset);
       
   495     if (compile_sed_expr(sed_cfg, cmd, arg) != APR_SUCCESS) {
       
   496         return apr_psprintf(cmd->temp_pool,
       
   497                             "Failed to compile sed expression. %s",
       
   498                             sed_cfg->last_error);
       
   499     }
       
   500     return NULL;
       
   501 }
       
   502 
       
   503 static void *create_sed_dir_config(apr_pool_t *p, char *s)
       
   504 {
       
   505     sed_config *cfg = apr_pcalloc(p, sizeof(sed_config));
       
   506     return cfg;
       
   507 }
       
   508 
       
   509 static const command_rec sed_filter_cmds[] = {
       
   510     AP_INIT_TAKE1("OutputSed", sed_add_expr,
       
   511                   (void *) APR_OFFSETOF(sed_config, output),
       
   512                   ACCESS_CONF,
       
   513                   "Sed regular expression for Response"),
       
   514     AP_INIT_TAKE1("InputSed", sed_add_expr,
       
   515                   (void *) APR_OFFSETOF(sed_config, input),
       
   516                   ACCESS_CONF,
       
   517                   "Sed regular expression for Request"),
       
   518     {NULL}
       
   519 };
       
   520 
       
   521 static void register_hooks(apr_pool_t *p)
       
   522 {
       
   523     ap_register_output_filter(sed_filter_name, sed_response_filter, NULL,
       
   524                               AP_FTYPE_RESOURCE);
       
   525     ap_register_input_filter(sed_filter_name, sed_request_filter, NULL,
       
   526                              AP_FTYPE_RESOURCE);
       
   527 }
       
   528 
       
   529 module AP_MODULE_DECLARE_DATA sed_module = {
       
   530     STANDARD20_MODULE_STUFF,
       
   531     create_sed_dir_config,      /* dir config creater */
       
   532     NULL,                       /* dir merger --- default is to override */
       
   533     NULL,                       /* server config */
       
   534     NULL,                       /* merge server config */
       
   535     sed_filter_cmds,            /* command table */
       
   536     register_hooks              /* register hooks */
       
   537 };