--- docs/manual/mod/mod_include.xml (revision 412403) +++ docs/manual/mod/mod_include.xml (working copy) @@ -313,11 +313,12 @@ MIME-type (text/plain, text/html etc.) will be included. Otherwise CGI scripts are invoked as normal using the complete URL given in - the command, including any query string.

+ the command, including any query string. POST data is not passed + to the script by default.

-

An attribute defines the location of the document; the - inclusion is done for each attribute given to the include - command. The valid attributes are:

+

A location attribute defines the location of the document; the + inclusion is done for each location attribute given to the include + command. The valid location attributes are:

file
@@ -353,6 +354,46 @@ into an HTML document.

+ +

In addition to location attributes, the inclusion might also have + parameter attributes given after the location attribute. + Valid parameter attribues are:

+ +
+
postconsumer
+
The flag controls the passing of data sent by the client in POST request. + By default, POST-data is not passed to the subrequest unless this flag is set to "yes". + Only one subrequest can be the postconsumer and thus have the POST data. +
+ +
add-headers
+

The value is a string containing whitespace separated list of expressions + defining which headers should be added to the response.

+

The syntax of one expression is following:

+ +
Expression :-> Rangedef Header-name
+Rangedef :-> Range ":" | empty
+Header-name :-> string
+Range :-> "*" | Value | Value "-" Value
+Value :-> "[0-9]+"
+
+ The header-name is the name of the header that is wanted to be added. + Values in range is either all(*), first n elements, a range (n-m). + If the range is omitted, first element is assumed. +
+ +
replace-headers
+
Similar to add-headers, but instead of adding the headers, the original headers + are replaced. If a special header status is replaced, then + the return status of the subrequest is passed to the main request. This is useful + especially when doing redirections. +
+
+ + Example + <!--#include virtual="/cgi-bin/example.cgi" postconsumer="yes" add-headers="*:Set-cookie" replace-headers="location status" --> + +
The printenv Element @@ -745,6 +786,24 @@ +SSIBuffering +Enables output buffering in SSI filtered +requests +SSIBuffering on|off +SSIBuffering off +server configvirtual host +directory.htaccess +Options + +

The SSIBuffering directive controls the buffering + of the output of SSI documents. You might want to enable buffering if you + want to pass response headers from some of the included documents that + are included in the middle of your document.

+
+ +
+ + XBitHack Parse SSI directives in files with the execute bit set --- modules/filters/mod_include.c (revision 412403) +++ modules/filters/mod_include.c (working copy) @@ -114,6 +114,7 @@ const char *default_time_fmt; const char *undefined_echo; xbithack_t xbithack; + int output_buffering; } include_dir_config; typedef struct { @@ -1489,6 +1490,189 @@ } +/* Loopback filter + * An output filter that passes all output to a destination + * bucket brigade given in the initialization phase. + */ + +struct loopback_ctx { + apr_bucket_brigade *pass_bb; +}; + +static ap_filter_t *create_loopback_filter(request_rec *r, + apr_bucket_brigade * destination) +{ + + ap_filter_t *f = (ap_filter_t *) apr_palloc(r->pool, sizeof(ap_filter_t)); + struct loopback_ctx *ctx = + (struct loopback_ctx *) apr_palloc(r->pool, + sizeof(struct loopback_ctx)); + ctx->pass_bb = destination; + f->ctx = ctx; + f->r = r; + f->c = NULL; + f->next = NULL; + f->frec = ap_get_output_filter_handle("LOOPBACK"); + return f; +} + +static apr_status_t loopback_filter(ap_filter_t * f, apr_bucket_brigade * b) +{ + request_rec *r = f->r; + struct loopback_ctx *ctx; + apr_bucket *e; + int eos = 0; + ctx = f->ctx; + ap_assert(ctx); + APR_BRIGADE_CONCAT(ctx->pass_bb, b); + return APR_SUCCESS; +} + + + +/* --------------------------- Header copier ------------------------------ + * A callback functionset that copies some elements of the table. + * + */ + + +struct header_transfer_ctx { + apr_pool_t *pool; + apr_table_t *dst; // Destination table where to write + apr_table_t *wanted; //Table mapping key to entries wanted + void (*table_addsetfn) (apr_table_t *, const char *, const char *); // Function to add to table + //Internal state + apr_hash_t *seen; //Hash mapping key to entries seen +}; + + +static struct header_transfer_ctx *create_ht_ctx(apr_pool_t * pool, + apr_table_t * dst, + const char *str, int replace) +{ + struct header_transfer_ctx *ctx = apr_palloc(pool, sizeof *ctx); + ctx->dst = dst; + ctx->pool = pool; + ctx->seen = apr_hash_make(pool); + ctx->wanted = apr_table_make(pool, 1); + + + ctx->table_addsetfn = replace ? apr_table_set : apr_table_add; + + /* + Elements are of form: + '*:string' -- All elements + '[0-9]+-[0-9]+:string' -- Elements n-m + '[0-9]+:string' -- First n elements + String is of form: + '(element) (element) (element) ...' + */ + + // Split element from strings + { + char *state; + char *s = apr_pstrdup(pool, str); + ap_regex_t *count_split = + ap_pregcomp(pool, "^(([^:]+):)?(.*)$", AP_REG_EXTENDED); + ap_regmatch_t m[4]; + s = apr_strtok(s, " \t", &state); + // Split element further + do { + if (ap_regexec(count_split, s, 4, m, 0) != 0) + return NULL; + if (m[3].rm_so == -1 || m[3].rm_so == m[3].rm_eo) // Errornenous pattern + return NULL; + char *key = + apr_pstrndup(pool, &s[m[3].rm_so], m[3].rm_eo - m[3].rm_so); + + if (m[2].rm_so == -1) // No range spesified, pass only first one + apr_table_addn(ctx->wanted, key, "1"); + else + apr_table_addn(ctx->wanted, key, + apr_pstrndup(pool, &s[m[2].rm_so], + m[2].rm_eo - m[2].rm_so)); + } while (NULL != (s = apr_strtok(NULL, " \t", &state))); + } + return ctx; +} + +static int transfer_headers(void *ctxptr, const char *key, const char *value) +{ + struct header_transfer_ctx *ctx = ctxptr; + /* Real implementation: */ + const char *match = apr_table_get(ctx->wanted, key); + char *p; + int *count; + if (!match) + return TRUE; // We will continue with new key + + if (match[0] == '*') { + ctx->table_addsetfn(ctx->dst, key, value); + return TRUE; + } + + // Some counting has to be done + count = apr_hash_get(ctx->seen, key, APR_HASH_KEY_STRING); + if (!count) { + count = apr_palloc(ctx->pool, sizeof(int)); + *count = 1; + apr_hash_set(ctx->seen, key, APR_HASH_KEY_STRING, count); + } + else { + (*count)++; + } + + + // Figure out the match syntax further + if (NULL != (p = index(match, '-'))) { // Has dash; patches between two numbers are accepted + if (*count < atoi(match) || *count > atoi(&p[1])) + return TRUE; + + } + else { // Only one number; patches <= number are accepted + if (*count > atoi(match)) + return TRUE; + } + + ctx->table_addsetfn(ctx->dst, key, value); + return TRUE; +} + +/* --------------------------- Input-blocking filter ---------------------- + * An input filter that blocks input by sending end of stream buckets as + * needed. + */ + +static int input_blocker(ap_filter_t * f, apr_bucket_brigade * bb, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes) +{ + if (f->r->proxyreq) { + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + APR_BRIGADE_INSERT_TAIL(bb, + apr_bucket_eos_create(f->r->connection-> + bucket_alloc)); + return APR_SUCCESS; +} + +static void drop_input_blocker(request_rec *r) +{ + ap_filter_rec_t *rec = ap_get_input_filter_handle("INCLUDES_IN"); + while (r->input_filters->frec == rec && r->input_filters->r == r) + ap_remove_input_filter(r->input_filters); +} + +static void add_input_blocker(request_rec *r) +{ + ap_filter_rec_t *rec = ap_get_input_filter_handle("INCLUDES_IN"); + if (r->input_filters->frec != rec) { + ap_add_input_filter_handle(rec, NULL, r, r->connection); + } + +} + + /* * +-------------------------------------------------------+ * | | @@ -1602,6 +1786,21 @@ } } + + +struct subrequest_t { + int type; + int is_postconsumer; + char *location; + char *add_headers; + char *replace_headers; +}; + +static void handle_one_subrq(include_ctx_t * ctx, ap_filter_t * f, + apr_bucket_brigade * bb, + struct subrequest_t *sr); + + /* * */ @@ -1609,6 +1808,7 @@ apr_bucket_brigade *bb) { request_rec *r = f->r; + struct subrequest_t sr = {0,0,NULL,NULL,NULL}; if (!ctx->argc) { ap_log_rerror(APLOG_MARK, @@ -1630,30 +1830,55 @@ while (1) { char *tag = NULL; char *tag_val = NULL; - request_rec *rr = NULL; - char *error_fmt = NULL; - char *parsed_string; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); if (!tag || !tag_val) { + handle_one_subrq(ctx,f,bb,&sr); break; } - if (strcmp(tag, "virtual") && strcmp(tag, "file")) { + if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) { + handle_one_subrq(ctx, f, bb, &sr); + memset(&sr, 0, sizeof(sr)); + sr.type = tag[0]; + sr.location = ap_ssi_parse_string(ctx, tag_val, NULL, 0, + SSI_EXPAND_DROP_NAME); + } + else if (!strcmp(tag, "postconsumer")) + sr.is_postconsumer = !strcasecmp(tag_val, "yes"); + else if (!strcmp(tag, "add-headers")) + sr.add_headers = tag_val; + else if (!strcmp(tag, "replace_headers")) + sr.replace_headers = tag_val; + else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter " "\"%s\" to tag include in %s", tag, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } + } - parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, - SSI_EXPAND_DROP_NAME); - if (tag[0] == 'f') { + return APR_SUCCESS; +} + +static void handle_one_subrq(include_ctx_t *ctx,ap_filter_t *f, + apr_bucket_brigade *bb, + struct subrequest_t *sr) { + + request_rec *r = f->r; + request_rec *rr = NULL; + char *error_fmt = NULL; + ap_filter_t *loopback; + + + if (sr->type) { + loopback = create_loopback_filter(r,bb); + if (sr->type == 'f') { char *newpath; apr_status_t rv; /* be safe; only files in this directory or below allowed */ - rv = apr_filepath_merge(&newpath, NULL, parsed_string, + rv = apr_filepath_merge(&newpath, NULL, sr->location, APR_FILEPATH_SECUREROOTTEST | APR_FILEPATH_NOTABSOLUTE, ctx->dpool); @@ -1661,11 +1886,29 @@ error_fmt = "unable to include file \"%s\" in parsed file %s"; } else { - rr = ap_sub_req_lookup_file(newpath, r, f->next); + rr = ap_sub_req_lookup_file(newpath, r, loopback); } } else { - rr = ap_sub_req_lookup_uri(parsed_string, r, f->next); + if (sr->is_postconsumer) + drop_input_blocker(r); + + rr = ap_sub_req_method_uri(r->method, sr->location, r, loopback); + if (sr->is_postconsumer) { + /* Pass POST-related headers */ + rr->headers_in = apr_table_copy(rr->pool, r->headers_in); + + /* Write Content-Length to clength-field. + * This is required for mod-jk to function correctly. + */ + char *lenp = + (char *) apr_table_get(r->headers_in, "Content-Length"); + if (lenp) { + int rc = atoi(lenp); + rr->clength= rc > 0 ? rc : 0; + } + } + } if (!error_fmt && rr->status != HTTP_OK) { @@ -1691,8 +1934,33 @@ error_fmt = "unable to include \"%s\" in parsed file %s"; } + if (!error_fmt && sr->replace_headers) { + struct header_transfer_ctx *hctx = + create_ht_ctx(r->pool, r->headers_out, sr->replace_headers, + TRUE); + if (hctx) { + apr_table_do(transfer_headers, hctx, rr->headers_out, NULL); + if (apr_table_get(hctx->wanted, "status")) + r->status = rr->status; + } + else + error_fmt = "unable to parse replace-headers of \"%s\" " + "in parsed file %s"; + } + + if (!error_fmt && sr->add_headers) { + struct header_transfer_ctx *hctx = + create_ht_ctx(r->pool, r->headers_out, sr->add_headers, + FALSE); + if (hctx) + apr_table_do(transfer_headers, hctx, rr->headers_out, NULL); + else + error_fmt = "unable to parse add-headers of \"%s\"" + "in parsed file %s"; + } + if (error_fmt) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, sr->location, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); } @@ -1701,13 +1969,8 @@ * variables in this r->subprocess_env in the subrequest's * r->pool, so that pool must survive as long as this request. * Yes, this is a memory leak. */ - - if (error_fmt) { - break; - } } - return APR_SUCCESS; } /* @@ -3052,7 +3315,7 @@ /* * This is the main loop over the current bucket brigade. */ -static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb) +static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb,int output_buffering) { include_ctx_t *ctx = f->ctx; struct ssi_internal_ctx *intern = ctx->intern; @@ -3137,8 +3400,8 @@ } /* enough is enough ... */ - if (ctx->flush_now || - intern->bytes_read > AP_MIN_BYTES_TO_WRITE) { + if (!output_buffering && + ( ctx->flush_now || intern->bytes_read > AP_MIN_BYTES_TO_WRITE)) { if (!APR_BRIGADE_EMPTY(pass_bb)) { rv = ap_pass_brigade(f->next, pass_bb); @@ -3195,7 +3458,7 @@ } newb = APR_BUCKET_NEXT(b); - if (ctx->flags & SSI_FLAG_PRINTING) { + if (ctx->flags & SSI_FLAG_PRINTING && index) { APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(pass_bb, b); } @@ -3487,6 +3750,7 @@ * a program - in either case a strong ETag header will likely be invalid. */ apr_table_setn(f->r->notes, "no-etag", ""); + add_input_blocker(f->r); return OK; } @@ -3506,6 +3770,7 @@ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "mod_include: Options +Includes (or IncludesNoExec) " "wasn't set, INCLUDES filter removed"); + drop_input_blocker(f->r); ap_remove_output_filter(f); return ap_pass_brigade(f->next, b); } @@ -3598,7 +3863,9 @@ ap_escape_shell_cmd(r->pool, arg_copy)); } - return send_parsed_content(f, b); + apr_status_t rc= send_parsed_content(f, b,conf->output_buffering); + drop_input_blocker(f->r); + return rc; } static int include_fixup(request_rec *r) @@ -3660,6 +3927,7 @@ result->default_time_fmt = DEFAULT_TIME_FORMAT; result->undefined_echo = DEFAULT_UNDEFINED_ECHO; result->xbithack = DEFAULT_XBITHACK; + result->output_buffering = FALSE; return result; } @@ -3762,6 +4030,12 @@ return NULL; } +static const char *set_output_buffering(cmd_parms *cmd,void *mconfig,int arg) +{ + include_dir_config *conf = (include_dir_config *) mconfig; + conf->output_buffering = arg; + return NULL; +} /* * +-------------------------------------------------------+ @@ -3801,6 +4075,9 @@ "Off, On, or Full"), AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, "a string"), + AP_INIT_FLAG("SSIBuffering", set_output_buffering, NULL, + OR_OPTIONS | RSRC_CONF, + "On or Off"), AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL, "a strftime(3) formatted string"), AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF, @@ -3826,6 +4103,10 @@ ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST); ap_register_output_filter("INCLUDES", includes_filter, includes_setup, AP_FTYPE_RESOURCE); + ap_register_input_filter("INCLUDES_IN", input_blocker, NULL, + AP_FTYPE_RESOURCE); + ap_register_output_filter("LOOPBACK", loopback_filter,NULL, + AP_FTYPE_PROTOCOL); } module AP_MODULE_DECLARE_DATA include_module =