--- 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 =