]> granicus.if.org Git - apache/commitdiff
Entering mod_filter and documentation, as previously discussed on dev@httpd.
authorNick Kew <niq@apache.org>
Fri, 10 Sep 2004 16:17:15 +0000 (16:17 +0000)
committerNick Kew <niq@apache.org>
Fri, 10 Sep 2004 16:17:15 +0000 (16:17 +0000)
Please test-drive in time for 2.2.  Currently compatible with 2.0 and up.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@105062 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/images/mod_filter_new.gif [new file with mode: 0644]
docs/manual/images/mod_filter_old.gif [new file with mode: 0644]
docs/manual/mod/mod_filter.html [new file with mode: 0644]
docs/manual/mod/mod_filter.html.en [new file with mode: 0644]
docs/manual/mod/mod_filter.xml [new file with mode: 0644]
docs/manual/mod/mod_filter.xml.meta [new file with mode: 0644]
modules/experimental/mod_filter.c [new file with mode: 0644]

diff --git a/docs/manual/images/mod_filter_new.gif b/docs/manual/images/mod_filter_new.gif
new file mode 100644 (file)
index 0000000..fb15c40
Binary files /dev/null and b/docs/manual/images/mod_filter_new.gif differ
diff --git a/docs/manual/images/mod_filter_old.gif b/docs/manual/images/mod_filter_old.gif
new file mode 100644 (file)
index 0000000..ec88078
Binary files /dev/null and b/docs/manual/images/mod_filter_old.gif differ
diff --git a/docs/manual/mod/mod_filter.html b/docs/manual/mod/mod_filter.html
new file mode 100644 (file)
index 0000000..4ed691a
--- /dev/null
@@ -0,0 +1,3 @@
+URI: mod_fiter.html.en
+Content-Language: en
+Content-type: text/html; charset=ISO-8859-1
diff --git a/docs/manual/mod/mod_filter.html.en b/docs/manual/mod/mod_filter.html.en
new file mode 100644 (file)
index 0000000..314040a
--- /dev/null
@@ -0,0 +1,387 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /><!--
+        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+              This file is generated from xml source: DO NOT EDIT
+        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+      -->
+<title>mod_filter - Apache HTTP Server</title>
+<link title="Main stylesheet" type="text/css" media="all" rel="stylesheet" href="../style/css/manual.css" />
+<link title="No Sidebar - Default font size" type="text/css" media="all" rel="alternate stylesheet" href="../style/css/manual-loose-100pc.css" />
+<link type="text/css" media="print" rel="stylesheet" href="../style/css/manual-print.css" />
+<link rel="shortcut icon" href="../images/favicon.ico" /></head>
+<body>
+<div id="page-header">
+<p class="menu"><a href="../mod/">Modules</a> | <a href="../mod/directives.html">Directives</a> | <a href="../faq/">FAQ</a> | <a href="../glossary.html">Glossary</a> | <a href="../sitemap.html">Sitemap</a></p>
+<p class="apache">Apache HTTP Server Version 2.1</p>
+<img src="../images/feather.gif" alt="" /></div>
+<div class="up"><a href="./"><img src="../images/left.gif" alt="&lt;-" title="&lt;-" /></a></div>
+<div id="path">
+<a href="http://www.apache.org/">Apache</a> &gt; <a href="http://httpd.apache.org/">HTTP Server</a> &gt; <a href="http://httpd.apache.org/docs-project/">Documentation</a> &gt; <a href="../">Version 2.1</a> &gt; <a href="./">Modules</a></div>
+<div id="page-content">
+<div id="preamble"><h1>Apache Module mod_filter</h1>
+<div class="toplang">
+<p><span>Available Languages: </span><a href="../en/mod/mod_filter.html" title="English">&nbsp;en&nbsp;</a></p>
+</div>
+<table class="module"><tr><th><a href="module-dict.html#Description">Description:</a></th><td>Context-sensitive smart filter configuration module</td></tr>
+<tr><th><a href="module-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="module-dict.html#ModuleIdentifier">Module Identifier:</a></th><td>filter_module</td></tr>
+<tr><th><a href="module-dict.html#SourceFile">Source File:</a></th><td>mod_filter.c</td></tr>
+<tr><th><a href="module-dict.html#Compatibility">Compatibility:</a></th><td>Apache 2.0 and higher</td></tr></table>
+<h3>Summary</h3>
+
+<p>This module enables smart, context-sensitive configuration of
+output content filters.  For example, apache can be configured to
+process different content-types through different filters, even
+when the content-type is not known in advance (e.g. in a proxy).
+</p>
+<p>mod_filter works by introducing indirection into the filter
+chain.  Instead of inserting filters in the chain, we insert
+a filter harness which in turn dispatches conditionally
+to a filter provider.  Any content filter may be used as a provider
+to mod_filter; no change to existing filter modules is required
+(although it may be possible to simplify them).
+</p>
+</div>
+<div id="quickview"><h3 class="directives">Directives</h3>
+<ul id="toc">
+<li><img src="../images/down.gif" alt="" /> <a href="#filterchain">FilterChain</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#filterdebug">FilterDebug</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#filterdeclare">FilterDeclare</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#filterprotocol">FilterProtocol</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#filterprovider">FilterProvider</a></li>
+</ul>
+<h3>Topics</h3>
+<ul id="topics">
+<li><img src="../images/down.gif" alt="" /> <a href="#smart">Smart Filtering</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#terms">Filter Declarations, Providers and Chains</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#config">Configuring the Chain</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#examples">Examples</a></li>
+<li><img src="../images/down.gif" alt="" /> <a href="#protocol">Protocol Handling</a></li>
+</ul></div>
+<div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="section">
+<h2><a id="smart" name="smart">Smart Filtering</a></h2>
+<p>In the traditional filtering model, filters are inserted unconditionally
+using <code class="directive"><a href="../mod/mod_mime.html#addoutputfilter">AddOutputFilter</a></code> and family.
+Each filter then needs to determine whether to run, and there is little
+flexibility available for server admins to allow the chain to be
+configured dynamically.</p>
+<p>mod_filter by contrast gives server administrators a great deal of
+flexibility in configuring the filter chain.  In fact, filters can be
+inserted based on any Request Header, Response Header or Environment
+Variable.  This generalises the limited flexibility offered by
+<code class="directive"><a href="../mod/core.html#addoutputfilterbytype">AddOutputFilterByType</a></code>, and fixes
+it to work correctly with dynamic content, regardless of the
+content generator.  The ability to dispatch based on Environment
+Variables offers the full flexibility of configuration with
+<code class="module"><a href="../mod/mod_rewrite.html">mod_rewrite</a></code> to anyone who needs it.</p>
+
+</div><div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="section">
+<h2><a id="terms" name="terms">Filter Declarations, Providers and Chains</a></h2>
+<img src="../images/mod_filter_old.gif" alt="" />
+<p>In the traditional model, output filters are a simple chain
+from the content generator (handler) to the client.  This works well
+provided the filter chain can be correctly configured, but presents
+problems when the filters need to be configured dynamically based on
+the outcome of the handler.</p>
+<img src="../images/mod_filter_new.gif" alt="" />
+<p>mod_filter works by introducing indirection into the filter
+chain.  Instead of inserting filters in the chain, we insert
+a filter harness which in turn dispatches conditionally
+to a filter provider.  Any content filter may be used as a provider
+to mod_filter; no change to existing filter modules is required
+(although it may be possible to simplify them).  There can be
+multiple providers for one filter, but no more than one provider will
+run for any single request.
+</p>
+<p>A filter chain comprises any number of instances of the filter
+harness, each of which may have any number of providers.  A special
+case is that of a single provider with unconditional dispatch: this
+is equivalent to inserting the provider filter directly into the chain.
+</p>
+</div><div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="section">
+<h2><a id="config" name="config">Configuring the Chain</a></h2>
+<p>There are three stages to configuring a filter chain with mod_filter.
+For details of the directives, see below.</p>
+<dl>
+<dt>Declare Filters</dt>
+<dd>The <code class="directive">FilterDeclare</code> directive declares a filter,
+assigning it a name and a dispatch criterion.</dd>
+<dt>Register Providers</dt>
+<dd>The <code class="directive">FilterProvider</code> directive registers a provider with a filter.
+The filter must have been registered with <code class="directive">FilterDeclare</code>.
+The provider must have been registered with
+<code>ap_register_output_filter</code> by some module.  The final argument
+to <code class="directive">FilterProvider</code> is a match string, that will be checked against
+the filter's dispatch criterion to determine whether to run this provider.</dd>
+<dt>Configure the Chain</dt>
+<dd>The above directives build components of a smart filter chain,
+but do not configure it to run.  The <code class="directive">FilterChain</code> directive
+builds a filter chain from smart filters declared, offering the
+flexibility to insert filters at the beginning or end of the chain,
+remove a filter, or clear the chain.</dd>
+</dl>
+</div><div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="section">
+<h2><a id="examples" name="examples">Examples</a></h2>
+<dl>
+<dt>Serverside Includes (SSI)</dt>
+<dd>
+<p>A simple case of using mod_filter in place of
+<code>AddOutputFilterByType</code></p>
+<div class="example"><p><code>
+FilterDeclare SSI Content-Type<br />
+FilterProvider SSI INCLUDES $text/html<br />
+FilterChain SSI<br />
+</code></p></div>
+</dd>
+<dt>Serverside Includes (SSI)</dt>
+<dd>
+<p>The same as the above but dispatching on handler (classic
+SSI behaviour; .shtml files get processed).</p>
+<div class="example"><p><code>
+FilterDeclare SSI Handler<br />
+FilterProvider SSI INCLUDES server-parsed<br />
+FilterChain SSI<br />
+</code></p></div>
+</dd>
+<dt>Emulating mod_gzip with mod_deflate</dt>
+<dd>
+<p>Insert INFLATE filter only if "gzip" is NOT in the
+Accept-Encoding header.</p>
+<div class="example"><p><code>
+FilterDeclare gzip req=Accept-Encoding<br />
+FilterProvider gzip inflate !$gzip<br />
+FilterChain gzip<br />
+</code></p></div>
+</dd>
+<dt>Image Downsampling</dt>
+<dd>
+<p>Suppose we want to downsample all web images, and have filters
+for GIF, JPEG and PNG.</p>
+<div class="example"><p><code>
+FilterDeclare unpack Content-Type<br />
+FilterProvider unpack jpeg_unpack $image/jpeg<br />
+FilterProvider unpack gif_unpack $image/gif<br />
+FilterProvider unpack png_unpack $image/png<br />
+FilterDeclare downsample Content-Type<br />
+FilterProvider downsample downsample_filter $image<br />
+FilterProtocol downsample "change=yes"<br />
+FilterDeclare repack Content-Type<br />
+FilterProvider repack jpeg_pack $image/jpeg<br />
+FilterProvider repack gif_pack $image/gif<br />
+FilterProvider repack png_pack $image/png<br />
+&lt;Location /image-filter&gt;<br />
+FilterChain unpack downsample repack<br />
+&lt;/Location&gt;
+</code></p></div>
+</dd>
+</dl>
+</div><div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="section">
+<h2><a id="protocol" name="protocol">Protocol Handling</a></h2>
+<p>Historically, each filter is responsible for ensuring that whatever
+changes it makes are correctly represented in the HTTP response headers,
+and that it does not run when it would make an illegal change.  This
+imposes a burden on filter authors to re-implement some common
+functionality in every filter:</p>
+<ul>
+<li>Many filters will change the content, invalidating existing content
+tags, checksums, hashes, and lengths.</li>
+<li>Filters that require an entire, unbroken response in input need to
+ensure they don't get byteranges from a backend.</li>
+<li>Filters that transform output in a filter need to ensure they don't
+violate a <code>Cache-Control: no-transform</code> header from the
+backend.</li>
+<li>Filters may make responses uncacheable.</li>
+</ul>
+<p>mod_filter aims to offer generic handling of these details of filter
+implementation, reducing the complexity required of content filter modules.
+This is work-in-progress; the <code class="directive">FilterProtocol</code>
+implements some of this functionality, but there are no API calls yet.</p>
+<p>At the same time, mod_filter should not interfere with a filter that
+wants to handle all aspects of the protocol.  By default (i.e. in the
+absence of any <code class="directive">FilterProtocol</code> directives), mod_filter
+will leave the headers untouched.</p>
+</div>
+<div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="directive-section"><h2><a id="FilterChain" name="FilterChain">FilterChain</a> <a id="filterchain" name="filterchain">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Configure the filter chain</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>FilterChain ([+=-@!]<var>filter-name</var>)+</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host, directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_filter</td></tr>
+</table>
+<p>This configures an actual filter chain, from declared filters.
+<code class="directive">FilterChain</code> takes any number of arguments,
+each optionally preceded with a single-character control that
+determines what to do:</p>
+<dl>
+<dt>+filter-name</dt>
+<dd>Add filter-name to the end of the filter chain</dd>
+<dt>@filter-name</dt>
+<dd>Insert filter-name at the start of the filter chain</dd>
+<dt>-filter-name</dt>
+<dd>Remove filter-name from the filter chain</dd>
+<dt>=filter-name</dt>
+<dd>Empty the filter chain and insert filter-name</dd>
+<dt>!</dt>
+<dd>Empty the filter chain</dd>
+<dt>filter-name</dt>
+<dd>Equivalent to +filter-name</dd>
+</dl>
+
+</div>
+<div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="directive-section"><h2><a id="FilterDebug" name="FilterDebug">FilterDebug</a> <a id="filterdebug" name="filterdebug">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Get debug/diagnostic information from mod_filter</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>FilterDebug filter-name level</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host, directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_filter</td></tr>
+</table>
+<p>This directive generates debug information from mod_filter.
+It is designed to help test and debug providers (filter modules), although
+it may also help with mod_filter itself.</p>
+<p>The debug output depends on the level set:</p>
+<dl>
+<dt>0 (default)</dt>
+<dd>No debug information is generated.</dd>
+<dt>1</dt>
+<dd>mod_filter will record buckets and brigades passing through the filter
+to the error log, before the provider has processed them.
+This is similar to the information generated by
+<a href="http://apache.webthing.com/mod_diagnostics/">mod_diagnostics</a>.
+</dd>
+<dt>2 (not yet implemented)</dt>
+<dd>Will dump the full data passing through to a tempfile before the provider.
+<strong>For single-user debug only</strong>; this will not
+support concurrent hits.</dd>
+</dl>
+
+</div>
+<div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="directive-section"><h2><a id="FilterDeclare" name="FilterDeclare">FilterDeclare</a> <a id="filterdeclare" name="filterdeclare">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Declare a smart filter</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>FilterDeclare <var>filter-name</var> [req|resp|env]=<var>dispatch</var>
+<var>[type]</var>
+</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host, directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_filter</td></tr>
+</table>
+<p>This directive declares an output filter together with a
+header or environment variable that will determine runtime
+configuration.  The first argument is a <code>filter-name</code>
+for use in <code class="directive">FilterProvider</code>,
+<code class="directive">FilterChain</code> and
+<code class="directive">FilterProtocol</code> directives.</p>
+<p>The second is a string with optional <code>req=</code>,
+<code>resp=</code> or <code>env=</code> prefix causing it
+to dispatch on (respectively) the request header, response
+header, or environment variable named.  In the absence of a
+prefix, it defaults to a response header.  A special case is the
+word "handler", which causes mod_filter to dispatch on the handler.</p>
+<p>The final (optional) argument
+is the type of filter, and takes values of <var>ap_filter_type</var>
+- namely <var>RESOURCE</var> (the default), <var>CONTENT_SET</var>,
+<var>PROTOCOL</var>, <var>TRANSCODE</var>, <var>CONNECTION</var>
+or <var>NETWORK</var>.
+</p>
+
+
+</div>
+<div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="directive-section"><h2><a id="FilterProtocol" name="FilterProtocol">FilterProtocol</a> <a id="filterprotocol" name="filterprotocol">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Deal with correct HTTP protocol handling</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>FilterProtocol filter-name [provider-name] "proto-flags"</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host, directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_filter</td></tr>
+</table>
+<p>This directs mod_filter to deal with ensuring the filter doesn't run
+when it shouldn't, and that the HTTP response headers are correctly set
+taking into account the effects of the filter.</p>
+<p>There are two forms of this directive.  With three arguments, it
+applies specifically to a filter-name and a provider for that filter.
+With two arguments it applies to a filter-name whenever the filter runs
+<em>any</em> provider.</p>
+<p>proto-flags is one or more of</p>
+<dl>
+<dt>change=yes</dt>
+<dd>The filter changes the content, including possibly the content length</dd>
+<dt>change=1:1</dt>
+<dd>The filter changes the content, but will not change the content length</dd>
+<dt>byteranges=no</dt>
+<dd>The filter cannot work on byteranges and requires complete input</dd>
+<dt>proxy=no</dt>
+<dd>The filter should not run in a proxy context</dd>
+<dt>proxy=transform</dt>
+<dd>The filter transforms the response in a manner incompatible with
+the HTTP <code>Cache-Control: no-transform</code> header.</dd>
+<dt>cache=no</dt>
+<dd>The filter renders the output uncacheable (eg by introducing randomised
+content changes)</dd>
+</dl>
+
+</div>
+<div class="top"><a href="#page-header"><img src="../images/up.gif" alt="top" /></a></div>
+<div class="directive-section"><h2><a id="FilterProvider" name="FilterProvider">FilterProvider</a> <a id="filterprovider" name="filterprovider">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Register a content filter</td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>FilterProvider <var>filter-name</var> <var>provider-name</var> <var>match</var></code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host, directory, .htaccess</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Extension</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>mod_filter</td></tr>
+</table>
+<p>This directive registers a <em>provider</em> for the smart filter.
+The provider will be called if and only if the <var>match</var> declared
+here matches the value of the header or environment variable declared
+as <var>dispatch</var> in the <code class="directive">FilterDeclare</code>
+directive that declared <var>filter-name</var>.</p>
+<p><var>filter-name</var> must have been declared with 
+<code class="directive">FilterDeclare</code>.  <var>provider-name</var> must have
+been registered by loading a module that registers the name with
+<code>ap_register_output_filter</code>.</p>
+<p>The <var>match</var> argument specifies a match that will be applied to
+the filter's <var>dispatch</var> criterion.  The match may be a string
+match (exact match or substring), a regexp, an integer (greater, lessthan
+or equals), or unconditional.  The first characters of the <var>match</var>
+argument determines this:</p>
+<p><strong>First</strong>, if the first character is an exclamation mark
+<strong>!</strong>, this reverses the rule, so the provider will be used
+if and only if the match <em>fails</em>.</p>
+<p><strong>Second</strong>, it interprets the first character excluding
+any leading ! as follows:</p>
+<dl>
+<dt>default</dt>
+<dd>exact match</dd>
+<dt>$</dt>
+<dd>substring match</dd>
+<dt>/</dt>
+<dd>regexp match</dd>
+<dt>=</dt>
+<dd>integer equality</dd>
+<dt>&lt;</dt>
+<dd>integer less-than</dd>
+<dt>&gt;</dt>
+<dd>integer greater-than</dd>
+<dt>*</dt>
+<dd>Unconditional match</dd>
+</dl>
+
+</div>
+</div>
+<div class="bottomlang">
+<p><span>Available Languages: </span><a href="../en/mod/mod_filter.html" title="English">&nbsp;en&nbsp;</a></p>
+</div><div id="footer">
+<p class="apache">Copyright 1999-2004 The Apache Software Foundation.<br />Licensed under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>.</p>
+<p class="menu"><a href="../mod/">Modules</a> | <a href="../mod/directives.html">Directives</a> | <a href="../faq/">FAQ</a> | <a href="../glossary.html">Glossary</a> | <a href="../sitemap.html">Sitemap</a></p></div>
+</body></html>
diff --git a/docs/manual/mod/mod_filter.xml b/docs/manual/mod/mod_filter.xml
new file mode 100644 (file)
index 0000000..5cfda34
--- /dev/null
@@ -0,0 +1,361 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<modulesynopsis metafile="mod_filter.xml.meta">
+
+<name>mod_filter</name>
+<description>Context-sensitive smart filter configuration module</description>
+<status>Extension</status>
+<sourcefile>mod_filter.c</sourcefile>
+<identifier>filter_module</identifier>
+<compatibility>Apache 2.0 and higher</compatibility>
+
+<summary>
+<p>This module enables smart, context-sensitive configuration of
+output content filters.  For example, apache can be configured to
+process different content-types through different filters, even
+when the content-type is not known in advance (e.g. in a proxy).
+</p>
+<p>mod_filter works by introducing indirection into the filter
+chain.  Instead of inserting filters in the chain, we insert
+a filter harness which in turn dispatches conditionally
+to a filter provider.  Any content filter may be used as a provider
+to mod_filter; no change to existing filter modules is required
+(although it may be possible to simplify them).
+</p>
+</summary>
+
+<section id="smart"><title>Smart Filtering</title>
+<p>In the traditional filtering model, filters are inserted unconditionally
+using <directive module="mod_mime">AddOutputFilter</directive> and family.
+Each filter then needs to determine whether to run, and there is little
+flexibility available for server admins to allow the chain to be
+configured dynamically.</p>
+<p>mod_filter by contrast gives server administrators a great deal of
+flexibility in configuring the filter chain.  In fact, filters can be
+inserted based on any Request Header, Response Header or Environment
+Variable.  This generalises the limited flexibility offered by
+<directive module="core">AddOutputFilterByType</directive>, and fixes
+it to work correctly with dynamic content, regardless of the
+content generator.  The ability to dispatch based on Environment
+Variables offers the full flexibility of configuration with
+<module>mod_rewrite</module> to anyone who needs it.</p>
+
+</section>
+<section id="terms"><title>Filter Declarations, Providers and Chains</title>
+<img src="../images/mod_filter_old.gif" alt=""/>
+<p>In the traditional model, output filters are a simple chain
+from the content generator (handler) to the client.  This works well
+provided the filter chain can be correctly configured, but presents
+problems when the filters need to be configured dynamically based on
+the outcome of the handler.</p>
+<img src="../images/mod_filter_new.gif" alt=""/>
+<p>mod_filter works by introducing indirection into the filter
+chain.  Instead of inserting filters in the chain, we insert
+a filter harness which in turn dispatches conditionally
+to a filter provider.  Any content filter may be used as a provider
+to mod_filter; no change to existing filter modules is required
+(although it may be possible to simplify them).  There can be
+multiple providers for one filter, but no more than one provider will
+run for any single request.
+</p>
+<p>A filter chain comprises any number of instances of the filter
+harness, each of which may have any number of providers.  A special
+case is that of a single provider with unconditional dispatch: this
+is equivalent to inserting the provider filter directly into the chain.
+</p>
+</section>
+<section id="config"><title>Configuring the Chain</title>
+<p>There are three stages to configuring a filter chain with mod_filter.
+For details of the directives, see below.</p>
+<dl>
+<dt>Declare Filters</dt>
+<dd>The <directive>FilterDeclare</directive> directive declares a filter,
+assigning it a name and a dispatch criterion.</dd>
+<dt>Register Providers</dt>
+<dd>The <directive>FilterProvider</directive> directive registers a provider with a filter.
+The filter must have been registered with <directive>FilterDeclare</directive>.
+The provider must have been registered with
+<code>ap_register_output_filter</code> by some module.  The final argument
+to <directive>FilterProvider</directive> is a match string, that will be checked against
+the filter's dispatch criterion to determine whether to run this provider.</dd>
+<dt>Configure the Chain</dt>
+<dd>The above directives build components of a smart filter chain,
+but do not configure it to run.  The <directive>FilterChain</directive> directive
+builds a filter chain from smart filters declared, offering the
+flexibility to insert filters at the beginning or end of the chain,
+remove a filter, or clear the chain.</dd>
+</dl>
+</section>
+<section id="examples"><title>Examples</title>
+<dl>
+<dt>Serverside Includes (SSI)</dt>
+<dd>
+<p>A simple case of using mod_filter in place of
+<code>AddOutputFilterByType</code></p>
+<example>
+FilterDeclare SSI Content-Type<br/>
+FilterProvider SSI INCLUDES $text/html<br/>
+FilterChain SSI<br/>
+</example>
+</dd>
+<dt>Serverside Includes (SSI)</dt>
+<dd>
+<p>The same as the above but dispatching on handler (classic
+SSI behaviour; .shtml files get processed).</p>
+<example>
+FilterDeclare SSI Handler<br/>
+FilterProvider SSI INCLUDES server-parsed<br/>
+FilterChain SSI<br/>
+</example>
+</dd>
+<dt>Emulating mod_gzip with mod_deflate</dt>
+<dd>
+<p>Insert INFLATE filter only if "gzip" is NOT in the
+Accept-Encoding header.</p>
+<example>
+FilterDeclare gzip req=Accept-Encoding<br/>
+FilterProvider gzip inflate !$gzip<br/>
+FilterChain gzip<br/>
+</example>
+</dd>
+<dt>Image Downsampling</dt>
+<dd>
+<p>Suppose we want to downsample all web images, and have filters
+for GIF, JPEG and PNG.</p>
+<example>
+FilterDeclare unpack Content-Type<br/>
+FilterProvider unpack jpeg_unpack $image/jpeg<br/>
+FilterProvider unpack gif_unpack $image/gif<br/>
+FilterProvider unpack png_unpack $image/png<br/>
+FilterDeclare downsample Content-Type<br/>
+FilterProvider downsample downsample_filter $image<br/>
+FilterProtocol downsample "change=yes"<br/>
+FilterDeclare repack Content-Type<br/>
+FilterProvider repack jpeg_pack $image/jpeg<br/>
+FilterProvider repack gif_pack $image/gif<br/>
+FilterProvider repack png_pack $image/png<br/>
+&lt;Location /image-filter&gt;<br/>
+FilterChain unpack downsample repack<br/>
+&lt;/Location&gt;
+</example>
+</dd>
+</dl>
+</section>
+<section id="protocol"><title>Protocol Handling</title>
+<p>Historically, each filter is responsible for ensuring that whatever
+changes it makes are correctly represented in the HTTP response headers,
+and that it does not run when it would make an illegal change.  This
+imposes a burden on filter authors to re-implement some common
+functionality in every filter:</p>
+<ul>
+<li>Many filters will change the content, invalidating existing content
+tags, checksums, hashes, and lengths.</li>
+<li>Filters that require an entire, unbroken response in input need to
+ensure they don't get byteranges from a backend.</li>
+<li>Filters that transform output in a filter need to ensure they don't
+violate a <code>Cache-Control: no-transform</code> header from the
+backend.</li>
+<li>Filters may make responses uncacheable.</li>
+</ul>
+<p>mod_filter aims to offer generic handling of these details of filter
+implementation, reducing the complexity required of content filter modules.
+This is work-in-progress; the <directive>FilterProtocol</directive>
+implements some of this functionality, but there are no API calls yet.</p>
+<p>At the same time, mod_filter should not interfere with a filter that
+wants to handle all aspects of the protocol.  By default (i.e. in the
+absence of any <directive>FilterProtocol</directive> directives), mod_filter
+will leave the headers untouched.</p>
+</section>
+
+<directivesynopsis>
+<name>FilterDeclare</name>
+<description>Declare a smart filter</description>
+<syntax>FilterDeclare <var>filter-name</var> [req|resp|env]=<var>dispatch</var>
+<var>[type]</var>
+</syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+
+<usage>
+<p>This directive declares an output filter together with a
+header or environment variable that will determine runtime
+configuration.  The first argument is a <code>filter-name</code>
+for use in <directive>FilterProvider</directive>,
+<directive>FilterChain</directive> and
+<directive>FilterProtocol</directive> directives.</p>
+<p>The second is a string with optional <code>req=</code>,
+<code>resp=</code> or <code>env=</code> prefix causing it
+to dispatch on (respectively) the request header, response
+header, or environment variable named.  In the absence of a
+prefix, it defaults to a response header.  A special case is the
+word "handler", which causes mod_filter to dispatch on the handler.</p>
+<p>The final (optional) argument
+is the type of filter, and takes values of <var>ap_filter_type</var>
+- namely <var>RESOURCE</var> (the default), <var>CONTENT_SET</var>,
+<var>PROTOCOL</var>, <var>TRANSCODE</var>, <var>CONNECTION</var>
+or <var>NETWORK</var>.
+</p>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>FilterProvider</name>
+<description>Register a content filter</description>
+<syntax>FilterProvider <var>filter-name</var> <var>provider-name</var> <var>match</var></syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+
+<usage>
+<p>This directive registers a <em>provider</em> for the smart filter.
+The provider will be called if and only if the <var>match</var> declared
+here matches the value of the header or environment variable declared
+as <var>dispatch</var> in the <directive>FilterDeclare</directive>
+directive that declared <var>filter-name</var>.</p>
+<p><var>filter-name</var> must have been declared with 
+<directive>FilterDeclare</directive>.  <var>provider-name</var> must have
+been registered by loading a module that registers the name with
+<code>ap_register_output_filter</code>.</p>
+<p>The <var>match</var> argument specifies a match that will be applied to
+the filter's <var>dispatch</var> criterion.  The match may be a string
+match (exact match or substring), a regexp, an integer (greater, lessthan
+or equals), or unconditional.  The first characters of the <var>match</var>
+argument determines this:</p>
+<p><strong>First</strong>, if the first character is an exclamation mark
+<strong>!</strong>, this reverses the rule, so the provider will be used
+if and only if the match <em>fails</em>.</p>
+<p><strong>Second</strong>, it interprets the first character excluding
+any leading ! as follows:</p>
+<dl>
+<dt>default</dt>
+<dd>exact match</dd>
+<dt>$</dt>
+<dd>substring match</dd>
+<dt>/</dt>
+<dd>regexp match</dd>
+<dt>=</dt>
+<dd>integer equality</dd>
+<dt>&lt;</dt>
+<dd>integer less-than</dd>
+<dt>&gt;</dt>
+<dd>integer greater-than</dd>
+<dt>*</dt>
+<dd>Unconditional match</dd>
+</dl>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>FilterChain</name>
+<description>Configure the filter chain</description>
+<syntax>FilterChain ([+=-@!]<var>filter-name</var>)+</syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+
+<usage>
+<p>This configures an actual filter chain, from declared filters.
+<directive>FilterChain</directive> takes any number of arguments,
+each optionally preceded with a single-character control that
+determines what to do:</p>
+<dl>
+<dt>+filter-name</dt>
+<dd>Add filter-name to the end of the filter chain</dd>
+<dt>@filter-name</dt>
+<dd>Insert filter-name at the start of the filter chain</dd>
+<dt>-filter-name</dt>
+<dd>Remove filter-name from the filter chain</dd>
+<dt>=filter-name</dt>
+<dd>Empty the filter chain and insert filter-name</dd>
+<dt>!</dt>
+<dd>Empty the filter chain</dd>
+<dt>filter-name</dt>
+<dd>Equivalent to +filter-name</dd>
+</dl>
+</usage>
+
+
+</directivesynopsis>
+
+
+<directivesynopsis>
+<name>FilterProtocol</name>
+<description>Deal with correct HTTP protocol handling</description>
+<syntax>FilterProtocol filter-name [provider-name] "proto-flags"</syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+<usage>
+<p>This directs mod_filter to deal with ensuring the filter doesn't run
+when it shouldn't, and that the HTTP response headers are correctly set
+taking into account the effects of the filter.</p>
+<p>There are two forms of this directive.  With three arguments, it
+applies specifically to a filter-name and a provider for that filter.
+With two arguments it applies to a filter-name whenever the filter runs
+<em>any</em> provider.</p>
+<p>proto-flags is one or more of</p>
+<dl>
+<dt>change=yes</dt>
+<dd>The filter changes the content, including possibly the content length</dd>
+<dt>change=1:1</dt>
+<dd>The filter changes the content, but will not change the content length</dd>
+<dt>byteranges=no</dt>
+<dd>The filter cannot work on byteranges and requires complete input</dd>
+<dt>proxy=no</dt>
+<dd>The filter should not run in a proxy context</dd>
+<dt>proxy=transform</dt>
+<dd>The filter transforms the response in a manner incompatible with
+the HTTP <code>Cache-Control: no-transform</code> header.</dd>
+<dt>cache=no</dt>
+<dd>The filter renders the output uncacheable (eg by introducing randomised
+content changes)</dd>
+</dl>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>FilterDebug</name>
+<description>Get debug/diagnostic information from mod_filter</description>
+<syntax>FilterDebug filter-name level</syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+<usage>
+<p>This directive generates debug information from mod_filter.
+It is designed to help test and debug providers (filter modules), although
+it may also help with mod_filter itself.</p>
+<p>The debug output depends on the level set:</p>
+<dl>
+<dt>0 (default)</dt>
+<dd>No debug information is generated.</dd>
+<dt>1</dt>
+<dd>mod_filter will record buckets and brigades passing through the filter
+to the error log, before the provider has processed them.
+This is similar to the information generated by
+<a href="http://apache.webthing.com/mod_diagnostics/">mod_diagnostics</a>.
+</dd>
+<dt>2 (not yet implemented)</dt>
+<dd>Will dump the full data passing through to a tempfile before the provider.
+<strong>For single-user debug only</strong>; this will not
+support concurrent hits.</dd>
+</dl>
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>
+
diff --git a/docs/manual/mod/mod_filter.xml.meta b/docs/manual/mod/mod_filter.xml.meta
new file mode 100644 (file)
index 0000000..2c0aadd
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<metafile>
+  <basename>mod_filter</basename>
+  <path>/mod/</path>
+  <relpath>..</relpath>
+
+  <variants>
+    <variant>en</variant>
+  </variants>
+</metafile>
diff --git a/modules/experimental/mod_filter.c b/modules/experimental/mod_filter.c
new file mode 100644 (file)
index 0000000..93dec28
--- /dev/null
@@ -0,0 +1,717 @@
+/* Copyright (C) 2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Originally by Nick Kew <nick@webthing.com>
+ *
+ * At the time of writing, this is designed primarily for use with
+ * httpd 2.2, but is also back-compatible with 2.0.  It is likely
+ * that the 2.0 and 2.2 versions may diverge in future, as additional
+ * capabilities for 2.2 are added, including updates to util_filter.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+/* apache */
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "util_filter.h"
+
+#ifndef NO_PROTOCOL
+#define PROTO_CHANGE 0x1
+#define PROTO_CHANGE_LENGTH 0x2
+#define PROTO_NO_BYTERANGE 0x4
+#define PROTO_NO_PROXY 0x8
+#define PROTO_NO_CACHE 0x10
+#define PROTO_TRANSFORM 0x20
+#endif
+
+module AP_MODULE_DECLARE_DATA filter_module ;
+
+typedef apr_status_t (*filter_func_t)(ap_filter_t*, apr_bucket_brigade*) ;
+
+typedef struct {
+    const char* name ;
+    filter_func_t func ;
+    void* fctx ;
+} harness_ctx ;
+
+typedef struct mod_filter_provider {
+    enum {
+        STRING_MATCH,
+        STRING_CONTAINS,
+        REGEX_MATCH,
+        INT_EQ,
+        INT_LT,
+        INT_GT,
+        DEFINED
+    } match_type ;
+    int not ;        /* negation on match_type */
+    union {
+        const char* c ;
+        regex_t* r ;
+        int i ;
+    } match ;
+    ap_filter_rec_t* frec ;
+    struct mod_filter_provider* next ;
+#ifndef NO_PROTOCOL
+    unsigned int proto_flags ;
+#endif
+} mod_filter_provider ;
+
+typedef struct {
+    ap_filter_rec_t frec ;
+    enum {
+        HANDLER,
+        REQUEST_HEADERS,
+        RESPONSE_HEADERS,
+        SUBPROCESS_ENV,
+        CONTENT_TYPE
+    } dispatch ;
+    const char* value ;
+    mod_filter_provider* providers ;
+    int debug ;
+#ifndef NO_PROTOCOL
+    unsigned int proto_flags ;
+    const char* range ;
+#endif
+} mod_filter_rec ;
+
+typedef struct mod_filter_chain {
+    const char* fname ;
+    struct mod_filter_chain* next ;
+} mod_filter_chain ;
+
+typedef struct {
+    apr_hash_t* live_filters ;
+    mod_filter_chain* chain ;
+} mod_filter_cfg ;
+
+const char* filter_bucket_type(apr_bucket* b)
+{
+    static struct {
+        const void* fn ;
+        const char* desc ;
+    } types[] = {
+        { &apr_bucket_type_heap, "HEAP" } ,
+        { &apr_bucket_type_transient, "TRANSIENT" } ,
+        { &apr_bucket_type_immortal, "IMMORTAL" } ,
+        { &apr_bucket_type_pool, "POOL" } ,
+        { &apr_bucket_type_eos, "EOS" } ,
+        { &apr_bucket_type_flush, "FLUSH" } ,
+        { &apr_bucket_type_file, "FILE" } ,
+        { &apr_bucket_type_mmap, "MMAP" } ,
+        { &apr_bucket_type_pipe, "PIPE" } ,
+        { &apr_bucket_type_socket, "SOCKET" } ,
+        { NULL, NULL }
+    } ;
+    int i = 0 ;
+    do {
+        if ( b->type == types[i].fn ) {
+            return types[i].desc ;
+        }
+    } while ( types[++i].fn != NULL ) ;
+    return "(error)" ;
+}
+static void filter_trace(apr_pool_t* pool, int debug, const char* fname,
+                         apr_bucket_brigade* bb)
+{
+    apr_bucket* b ;
+    const char* type ;
+    switch ( debug ) {
+        case 0:        /* normal, operational use */
+            return ;
+        case 1:        /* mod_diagnostics level */
+            ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, pool, fname);
+            for ( b = APR_BRIGADE_FIRST(bb) ;
+                  b != APR_BRIGADE_SENTINEL(bb) ;
+                  b = APR_BUCKET_NEXT(b) ) {
+                type = filter_bucket_type(b) ;
+                ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, pool, "   %s: %s %d",
+                              fname, filter_bucket_type(b), b->length);
+            }
+            break ;
+    }
+}
+
+static int filter_init(ap_filter_t* f)
+{
+    mod_filter_provider* p ;
+    int err = OK ;
+    harness_ctx* ctx = f->ctx ;
+    mod_filter_cfg* cfg
+        = ap_get_module_config(f->r->per_dir_config, &filter_module);
+    mod_filter_rec* filter
+        = apr_hash_get(cfg->live_filters, ctx->name, APR_HASH_KEY_STRING) ;
+    for ( p = filter->providers ; p ; p = p->next ) {
+        if ( p->frec->filter_init_func ) {
+            if ( err =  p->frec->filter_init_func(f), err != OK ) {
+                break ;        /* if anyone errors out here, so do we */
+            }
+        }
+    }
+    return err ;
+}
+static filter_func_t filter_lookup(request_rec* r, mod_filter_rec* filter)
+{
+    mod_filter_provider* provider ;
+    const char* str ;
+    char* str1 ;
+    int match ;
+    unsigned int proto_flags ;
+
+    /* Check registered providers in order */
+    for ( provider = filter->providers; provider; provider = provider->next) {
+        match = 1 ;
+        switch ( filter->dispatch ) {
+            case REQUEST_HEADERS:
+                str = apr_table_get(r->headers_in, filter->value) ;
+                break ;
+            case RESPONSE_HEADERS:
+                str = apr_table_get(r->headers_out, filter->value) ;
+                break ;
+            case SUBPROCESS_ENV:
+                str = apr_table_get(r->subprocess_env, filter->value) ;
+                break ;
+            case CONTENT_TYPE:
+                str = r->content_type ;
+                break ;
+            case HANDLER:
+                str = r->handler ;
+                break ;
+        }
+        /* treat nulls so we don't have to check every strcmp individually
+         * Not sure if there's anything better to do with them
+         */
+        if ( str == NULL ) {
+            if ( provider->match_type == DEFINED ) {
+                if ( provider->match.c != NULL ) {
+                    match = 0 ;
+                }
+            }
+        } else if ( provider->match.c == NULL ) {
+            match = 0 ;
+        } else {
+            /* Now we have no nulls, so we can do string and regexp matching */
+            switch ( provider->match_type ) {
+                case STRING_MATCH:
+                    if ( strcasecmp(str, provider->match.c) ) {
+                        match = 0 ;
+                    }
+                    break ;
+                case STRING_CONTAINS:
+                    str1 = apr_pstrdup(r->pool, str) ;
+                    ap_str_tolower(str1) ;
+                    if ( !strstr(str1, provider->match.c) ) {
+                        match = 0 ;
+                    }
+                    break ;
+                case REGEX_MATCH:
+                    if ( ap_regexec(provider->match.r, str, 0, NULL, 0)
+                        == REG_NOMATCH ) {
+                    match = 0 ;
+                    }
+                    break ;
+                case INT_EQ:
+                    if ( atoi(str) != provider->match.i ) {
+                        match = 0 ;
+                    }
+                    break ;
+                case INT_LT:
+                    if ( atoi(str) < provider->match.i ) {
+                        match = 0 ;
+                    }
+                    break ;
+                case INT_GT:
+                    if ( atoi(str) > provider->match.i ) {
+                        match = 0 ;
+                    }
+                    break ;
+                case DEFINED:        /* we already handled this:-) */
+                    break ;
+            }
+        }
+        if ( match != provider->not ) {
+            /* condition matches this provider */
+#ifndef NO_PROTOCOL
+            /* check protocol
+             *
+             * FIXME:
+             * This is a quick hack and almost certainly buggy.
+             * The idea is that by putting this in mod_filter, we relieve
+             * filter implementations of the burden of fixing up HTTP headers
+             * for cases that are routinely affected by filters.
+             * 
+             * Default is ALWAYS to do nothing, so as not to tread on the
+             * toes of filters which want to do it themselves.
+             * 
+             */
+            proto_flags = filter->proto_flags | provider->proto_flags ;
+
+            /* some specific things can't happen in a proxy */
+            if ( r->proxyreq ) {
+                if ( proto_flags & PROTO_NO_PROXY ) {
+                    /* can't use this provider; try next */
+                    continue ;
+                }
+                if ( proto_flags & PROTO_TRANSFORM ) {
+                    str = apr_table_get(r->headers_out, "Cache-Control") ;
+                    if ( str ) {
+                        str1 = apr_pstrdup(r->pool, str) ;
+                        ap_str_tolower(str1) ;
+                        if ( strstr(str1, "no-transform") ) {
+                            /* can't use this provider; try next */
+                            continue ;
+                        }
+                    }
+                    apr_table_addn(r->headers_out, "Warning", apr_psprintf(
+                      r->pool, "214 %s Transformation applied", r->hostname) ) ;
+                }
+            }
+            /* things that are invalidated if the filter transforms content */
+            if ( proto_flags & PROTO_CHANGE ) {
+                apr_table_unset(r->headers_out, "Content-MD5") ;
+                apr_table_unset(r->headers_out, "ETag") ;
+                if ( proto_flags & PROTO_CHANGE_LENGTH ) {
+                    apr_table_unset(r->headers_out, "Content-Length") ;
+                }
+            }
+            /* no-cache is for a filter that has different effect per-hit */
+            if ( proto_flags & PROTO_NO_CACHE ) {
+                apr_table_unset(r->headers_out, "Last-Modified") ;
+                apr_table_addn(r->headers_out, "Cache-Control", "no-cache") ;
+            }
+            if ( proto_flags & PROTO_NO_BYTERANGE ) {
+                apr_table_unset(r->headers_out, "Accept-Ranges") ;
+            } else if ( filter->range ) {
+                apr_table_setn(r->headers_in, "Range", filter->range) ;
+            }
+#endif
+            return provider->frec->filter_func.out_func ;
+        }
+    }
+    /* No provider matched */
+    return NULL ;
+}
+static apr_status_t filter_harness(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+
+    apr_status_t ret ;
+    const char* cachecontrol ;
+    char* str ;
+    harness_ctx* ctx = f->ctx ;
+    mod_filter_rec* filter = (mod_filter_rec*)f->frec ;
+
+    if ( f->r->status != 200 ) {
+        ap_remove_output_filter(f) ;
+        return ap_pass_brigade(f->next, bb) ;
+    }
+    filter_trace(f->c->pool, filter->debug, ctx->name, bb) ;
+
+/* look up a handler function if we haven't already set it */
+    if ( ! ctx->func ) {
+
+#ifndef NO_PROTOCOL
+        if ( f->r->proxyreq ) {
+            if ( filter->proto_flags & PROTO_NO_PROXY ) {
+                ap_remove_output_filter(f) ;
+                return ap_pass_brigade(f->next, bb) ;
+            }
+            if ( filter->proto_flags & PROTO_TRANSFORM ) {
+                cachecontrol = apr_table_get(f->r->headers_out, "Cache-Control") ;
+                if ( cachecontrol ) {
+                    str = apr_pstrdup(f->r->pool,  cachecontrol) ;
+                    ap_str_tolower(str) ;
+                    if ( strstr(str, "no-transform") ) {
+                        ap_remove_output_filter(f) ;
+                        return ap_pass_brigade(f->next, bb) ;
+                    }
+                }
+            }
+        }
+#endif
+        ctx->func = filter_lookup(f->r, filter) ;
+        if ( ! ctx->func ) {
+            ap_remove_output_filter(f) ;
+            return ap_pass_brigade(f->next, bb) ;
+        }
+    }
+
+    /* call the content filter with its own context, then restore our context */
+    f->ctx = ctx->fctx ;
+    ret = ctx->func(f, bb) ;
+    ctx->fctx = f->ctx ;
+    f->ctx = ctx ;
+    return ret ;
+}
+
+#ifndef NO_PROTOCOL
+static const char* filter_protocol(cmd_parms* cmd, void* CFG,
+        const char* fname, const char* pname, const char* proto)
+{
+
+    static const char* sep = " ;,      " ;
+    char* arg ;
+    char* tok = 0 ;
+    unsigned int flags = 0 ;
+    mod_filter_cfg* cfg = CFG ;
+    mod_filter_provider* provider = NULL ;
+    mod_filter_rec* filter
+        = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING) ;
+
+    if ( !provider ) {
+        return "FilterProtocol: No such filter" ;
+    }
+
+    /* Fixup the args: it's really pname that's optional */
+    if ( proto == NULL ) {
+        proto = pname ;
+        pname = NULL ;
+    } else {
+        /* Find provider */
+        for ( provider = filter->providers; provider; provider = provider->next ) {
+            if ( !strcasecmp(provider->frec->name, pname) )
+                break ;
+        }
+        if ( !provider ) {
+            return "FilterProtocol: No such provider for this filter" ;
+        }
+    }
+    /* Now set flags from our args */
+    for ( arg = apr_strtok(apr_pstrdup(cmd->pool, proto), sep, &tok) ;
+        arg ; arg = apr_strtok(NULL, sep, &tok) ) {
+        if ( !strcasecmp(arg, "change=yes") ) {
+            flags != PROTO_CHANGE | PROTO_CHANGE_LENGTH ;
+        } else if ( !strcasecmp(arg, "change=1:1") ) {
+            flags |= PROTO_CHANGE ;
+        } else if ( !strcasecmp(arg, "byteranges=no") ) {
+            flags |= PROTO_NO_BYTERANGE ;
+        } else if ( !strcasecmp(arg, "proxy=no") ) {
+            flags |= PROTO_NO_PROXY ;
+        } else if ( !strcasecmp(arg, "proxy=transform") ) {
+            flags |= PROTO_TRANSFORM ;
+        } else if ( !strcasecmp(arg, "cache=no") ) {
+            flags |= PROTO_NO_CACHE ;
+        }
+    }
+    if ( pname ) {
+        provider->proto_flags = flags ;
+    } else {
+        filter->proto_flags = flags ;
+    }
+    return NULL ;
+}
+#endif
+
+static const char* filter_declare(cmd_parms* cmd, void* CFG,
+        const char* fname, const char* condition, const char* place)
+{
+
+    const char* eq ;
+    char* tmpname = "" ;
+
+    mod_filter_cfg* cfg = (mod_filter_cfg*)CFG ;
+    mod_filter_rec* filter ;
+
+    filter = apr_pcalloc(cmd->pool, sizeof(mod_filter_rec)) ;
+    apr_hash_set(cfg->live_filters, fname, APR_HASH_KEY_STRING, filter) ;
+
+    filter->frec.name = fname ;
+    filter->frec.filter_init_func = filter_init ;
+    filter->frec.filter_func.out_func = filter_harness ;
+    filter->frec.ftype = AP_FTYPE_RESOURCE ;
+    filter->frec.next = NULL ;
+
+    /* determine what this filter will dispatch on */
+    eq = strchr(condition, '=') ;
+    if ( eq ) {
+        tmpname = apr_pstrdup(cmd->pool, eq+1) ;
+        if ( !strncasecmp(condition, "env=", 4) ) {
+            filter->dispatch = SUBPROCESS_ENV ;
+        } else if ( !strncasecmp(condition, "req=", 4) ) {
+            filter->dispatch = REQUEST_HEADERS ;
+        } else if ( !strncasecmp(condition, "resp=", 5) ) {
+            filter->dispatch = RESPONSE_HEADERS ;
+        } else {
+            return "FilterCondition: unrecognised dispatch table" ;
+        }
+    } else {
+        if ( !strcasecmp(condition, "handler") ) {
+            filter->dispatch = HANDLER ;
+        } else {
+            filter->dispatch = RESPONSE_HEADERS ;
+            tmpname = apr_pstrdup(cmd->pool, condition) ;
+            ap_str_tolower(tmpname) ;
+        }
+    }
+    if ( ( filter->dispatch == RESPONSE_HEADERS )
+        && !strcmp(tmpname, "content-type") ) {
+        filter->dispatch = CONTENT_TYPE ;
+    }
+    filter->value = tmpname ;
+
+    if ( place ) {
+        if ( !strcasecmp(place, "CONTENT_SET") ) {
+            filter->frec.ftype = AP_FTYPE_CONTENT_SET ;
+        } else if ( !strcasecmp(place, "PROTOCOL") ) {
+            filter->frec.ftype = AP_FTYPE_PROTOCOL ;
+        } else if ( !strcasecmp(place, "CONNECTION") ) {
+            filter->frec.ftype = AP_FTYPE_CONNECTION ;
+        } else if ( !strcasecmp(place, "NETWORK") ) {
+            filter->frec.ftype = AP_FTYPE_NETWORK ;
+        }
+    }
+
+    return NULL ;
+}
+
+static const char* filter_provider(cmd_parms* cmd, void* CFG,
+        const char* fname, const char* pname, const char* match)
+{
+    int flags ;
+    mod_filter_provider* provider ;
+    const char* rxend ;
+    const char* c ;
+    char* str ;
+
+    /* fname has been declared with DeclareFilter, so we can look it up */
+    mod_filter_cfg* cfg = CFG ;
+    mod_filter_rec* frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING) ;
+    /* provider has been registered, so we can look it up */
+    ap_filter_rec_t* provider_frec = ap_get_output_filter_handle(pname) ;
+    if ( ! frec ) {
+        return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname) ;
+    } else if ( !provider_frec ) {
+        return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname) ;
+    } else {
+        provider = apr_palloc(cmd->pool, sizeof(mod_filter_provider) ) ;
+        if ( match[0] == '!' ) {
+            provider->not = 1 ;
+            ++match ;
+        }
+        switch ( match[0] ) {
+            case '<':
+                provider->match_type = INT_LT ;
+                provider->match.i = atoi(match+1) ;
+                break ;
+            case '>':
+                provider->match_type = INT_GT ;
+                provider->match.i = atoi(match+1) ;
+                break ;
+            case '=':
+                provider->match_type = INT_EQ ;
+                provider->match.i = atoi(match+1) ;
+                break ;
+            case '/':
+                provider->match_type = REGEX_MATCH ;
+                rxend = strchr(match+1, '/') ;
+                if ( !rxend ) {
+                      return "Bad regexp syntax" ;
+                }
+                flags = REG_NOSUB ;        /* we're not mod_rewrite:-) */
+                for ( c = rxend+1; *c; ++c ) {
+                    switch (*c) {
+                        case 'i': flags |= REG_ICASE ; break ;
+                        case 'x': flags |= REG_EXTENDED ; break ;
+                    }
+                }
+                provider->match.r = ap_pregcomp(cmd->pool,
+                apr_pstrndup(cmd->pool, match+1, rxend-match-1), flags) ;
+                break ;
+            case '*':
+                provider->match_type = DEFINED ;
+                provider->match.i = -1 ;
+                break ;
+            case '$':
+                provider->match_type = STRING_CONTAINS ;
+                str = apr_pstrdup(cmd->pool, match+1) ;
+                ap_str_tolower(str) ;
+                provider->match.c = str ;
+                break ;
+            default:
+                provider->match_type = STRING_MATCH ;
+                provider->match.c = apr_pstrdup(cmd->pool, match) ;
+                break ;
+        }
+        provider->frec = provider_frec ;
+        provider->next = frec->providers ;
+        frec->providers = provider ;
+    }
+    return NULL ;
+}
+static const char* filter_chain(cmd_parms* cmd, void* CFG, const char* arg)
+{
+    mod_filter_chain* p ;
+    mod_filter_chain* q ;
+    mod_filter_cfg* cfg = CFG ;
+
+    switch (arg[0]) {
+        case '+':        /* add to end of chain */
+            p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)) ;
+            p->fname = arg+1 ;
+            if ( cfg->chain ) {
+                for ( q = cfg->chain ; q->next ; q = q->next ) ;
+                q->next = p ;
+            } else {
+                cfg->chain = p ;
+            }
+            break ;
+        case '@':        /* add to start of chain */
+            p = apr_palloc(cmd->pool, sizeof(mod_filter_chain)) ;
+            p->fname = arg+1 ;
+            p->next = cfg->chain ;
+            cfg->chain = p ;
+            break ;
+        case '-':        /* remove from chain */
+            if ( cfg->chain ) {
+                if ( strcasecmp(cfg->chain->fname, arg+1) ) {
+                    for ( p = cfg->chain ; p->next ; p = p->next ) {
+                        if ( !strcasecmp(p->next->fname, arg+1) ) {
+                            p->next = p->next->next ;
+                        }
+                    }
+                } else {
+                    cfg->chain = cfg->chain->next ;
+                }
+            }
+            break ;
+        case '!':        /* Empty the chain */
+            cfg->chain = NULL ;
+            break ;
+        case '=':        /* initialise chain with this arg */
+            p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)) ;
+            p->fname = arg+1 ;
+            cfg->chain = p ;
+            break ;
+        default:        /* add to end */
+            p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)) ;
+            p->fname = arg ;
+            if ( cfg->chain ) {
+                for ( q = cfg->chain ; q->next ; q = q->next ) ;
+                q->next = p ;
+            } else {
+                cfg->chain = p ;
+            }
+            break ;
+    }
+    return NULL ;
+}
+static const char* filter_debug(cmd_parms* cmd, void* CFG,
+        const char* fname, const char* level){
+    mod_filter_cfg* cfg = CFG ;
+    mod_filter_rec* frec = apr_hash_get(cfg->live_filters, fname,
+        APR_HASH_KEY_STRING) ;
+    frec->debug = atoi(level) ;
+    return NULL ;
+}
+
+static const command_rec filter_cmds[] = {
+    AP_INIT_TAKE23("FilterDeclare", filter_declare, NULL, OR_ALL,
+        "filter-name, dispatch-criterion [, filter-type]") ,
+    AP_INIT_TAKE3("FilterProvider", filter_provider, NULL, OR_ALL,
+        "filter-name, provider-name, dispatch-match") ,
+    AP_INIT_ITERATE("FilterChain", filter_chain, NULL, OR_ALL,
+        "list of filter names with optional [+-=!@]") ,
+    AP_INIT_TAKE2("FilterDebug", filter_debug, NULL, OR_ALL, "Debug level") ,
+#ifndef NO_PROTOCOL
+    AP_INIT_TAKE23("FilterProtocol", filter_protocol, NULL, OR_ALL,
+        "filter-name [provider-name] protocol-args") ,
+#endif
+    { NULL }
+} ;
+
+static int filter_insert(request_rec* r)
+{
+    mod_filter_chain* p ;
+    mod_filter_rec* filter ;
+    harness_ctx* fctx ;
+    mod_filter_cfg* cfg = ap_get_module_config(r->per_dir_config, &filter_module) ;
+#ifndef NO_PROTOCOL
+    int ranges = 1 ;
+#endif
+
+    for ( p = cfg->chain ; p ; p = p->next ) {
+        filter = apr_hash_get(cfg->live_filters, p->fname, APR_HASH_KEY_STRING) ;
+        fctx = apr_pcalloc(r->pool, sizeof(harness_ctx)) ;
+        fctx->name = p->fname ;
+        ap_add_output_filter_handle(&filter->frec, fctx, r, r->connection) ;
+#ifndef NO_PROTOCOL
+        if ( ranges && (filter->proto_flags & (PROTO_NO_BYTERANGE|PROTO_CHANGE_LENGTH)) ) {
+            filter->range = apr_table_get(r->headers_in, "Range") ;
+            apr_table_unset(r->headers_in, "Range") ;
+            ranges = 0 ;
+        }
+#endif
+    }
+    return OK ;
+}
+static void filter_hooks(apr_pool_t* pool)
+{
+    ap_hook_insert_filter(filter_insert, NULL, NULL, APR_HOOK_MIDDLE) ;
+}
+
+static void* filter_config(apr_pool_t* pool, char* x)
+{
+    mod_filter_cfg* cfg = apr_palloc(pool, sizeof(mod_filter_cfg) ) ;
+    cfg->live_filters = apr_hash_make(pool) ;
+    cfg->chain = NULL ;
+    return cfg ;
+}
+static void* filter_merge(apr_pool_t* pool, void* BASE, void* ADD)
+{
+    mod_filter_cfg* base = BASE ;
+    mod_filter_cfg* add = ADD ;
+    mod_filter_chain* savelink = 0 ;
+    mod_filter_chain* newlink ;
+    mod_filter_chain* p ;
+    mod_filter_cfg* conf = apr_palloc(pool, sizeof(mod_filter_cfg)) ;
+
+    conf->live_filters
+        = apr_hash_overlay(pool, add->live_filters, base->live_filters) ;
+    if ( base->chain && add->chain ) {
+        for ( p = base->chain ; p ; p = p->next ) {
+            newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain)) ;
+            if ( savelink ) {
+                savelink->next = newlink ;
+                savelink = newlink ;
+            } else {
+                conf->chain = savelink = newlink ;
+            }
+        }
+        for ( p = add->chain ; p ; p = p->next ) {
+            newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain)) ;
+            savelink->next = newlink ;
+            savelink = newlink ;
+        }
+    } else if ( add->chain ) {
+        conf->chain = add->chain ;
+    } else {
+        conf->chain = base->chain ;
+    }
+    return conf ;
+}
+module AP_MODULE_DECLARE_DATA filter_module = {
+    STANDARD20_MODULE_STUFF,
+    filter_config,
+    filter_merge,
+    NULL,
+    NULL,
+    filter_cmds,
+    filter_hooks
+} ;
+