]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
rewrite guide: document URL encoding/decoding pipeline
authorRich Bowen <rbowen@apache.org>
Thu, 14 May 2026 20:12:01 +0000 (20:12 +0000)
committerRich Bowen <rbowen@apache.org>
Thu, 14 May 2026 20:12:01 +0000 (20:12 +0000)
tech.xml: new "URL Encoding and Decoding" section explaining that
httpd unescapes the URI before pattern matching, how to use
%{THE_REQUEST} for the raw form, AllowEncodedSlashes options, and
a summary of the [B]/[BNP]/[NE] flags with links to flags.xml.

flags.xml: add cross-references to tech.html#encoding from the [B],
[BNP], and [NE] flag sections. Restore section headers for flag_bnp
and flag_bctls that were inadvertently dropped.

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

docs/manual/rewrite/TODO.md
docs/manual/rewrite/flags.xml
docs/manual/rewrite/tech.xml

index 705b876746ac2c9103e83995a0720c612538dcf7..06bd28adfb0ee7e390ba694df73e182a0c2b801d 100644 (file)
@@ -66,7 +66,7 @@ address. Sorted by priority.
       Behind a reverse proxy, check %{HTTP:X-Forwarded-Proto} instead.
       Add to the HTTPS redirect recipe in remapping.xml.
 
-- [ ] **URL encoding pipeline** — Apache decodes percent-encoded chars
+- [x] **URL encoding pipeline** — Apache decodes percent-encoded chars
       before pattern matching. %{THE_REQUEST} preserves the raw form.
       AllowEncodedSlashes, [B]/[NE]/[BNP] flags. No coherent
       explanation exists in the guide. Could be a new section in
index 1e586b8a1d09d854b42d41ee6215cf430a35bfb9..ae1c13e01ac59c4c180dae51c5b16dadda63eb3d 100644 (file)
@@ -123,8 +123,12 @@ RewriteRule "^search/(.*)$" "/search.php?term=$1" "[B= ?]"
 
 <p>To limit the characters escaped this way, see <a href="#flag_bne">#flag_bne</a>
         and <a href="#flag_bctls">#flag_bctls</a></p>
+
+<p>See <a href="tech.html#encoding">URL Encoding and Decoding</a> for
+a full explanation of how Apache decodes URIs before pattern matching.</p>
 </section>
 
+
 <section id="flag_bnp"><title>BNP|backrefnoplus (don't escape space to +)</title>
 <p>The [BNP] flag instructs <directive
 module="mod_rewrite">RewriteRule</directive> to escape the space character
@@ -139,8 +143,12 @@ RewriteRule "^search/(.*)$" "/search.php/$1" "[B,BNP]"
 
 
 <p>This flag is available in version 2.4.26 and later.</p>
+
+<p>See <a href="tech.html#encoding">URL Encoding and Decoding</a> for
+background on how encoding is handled in the rewrite pipeline.</p>
 </section>
 
+
 <section id="flag_bctls"><title>BCTLS</title>
 <p>The [BCTLS] flag is similar to the [B] flag, but only escapes
 control characters and the space character. This is the same set of
@@ -583,6 +591,9 @@ being converted to its hexcode equivalent, <code>%23</code>, which will
 then result in a 404 Not Found error condition.
 </p>
 
+<p>See <a href="tech.html#encoding">URL Encoding and Decoding</a> for
+the full picture of how Apache encodes and decodes URIs during
+rewriting.</p>
 </section>
 
 <section id="flag_ns"><title>NS|nosubreq</title>
index d70ae1fe3994a8da415344f7ef22bcc63c711c45..e73de19ddd2481e16c344495e2fab003c3ea01aa 100644 (file)
@@ -129,6 +129,77 @@ RewriteRule "^/old" "/other" [L]
 
 </section>
 
+<section id="encoding"><title>URL Encoding and Decoding</title>
+
+    <p>Apache httpd unescapes URL-encoded characters in the request URI before any
+    <directive module="mod_rewrite">RewriteRule</directive> pattern
+    matching takes place. A request for
+    <code>/my%20page/cats%3Fdogs</code> is decoded to
+    <code>/my page/cats?dogs</code>, and that decoded string is what the
+    <code>RewriteRule</code> pattern matches against.</p>
+
+    <p>This means you cannot write a pattern that matches the literal
+    URL-encoded form. If you need to distinguish
+    <code>/horses%2Fponies</code> from <code>/horses/ponies</code>, use
+    <code>%{THE_REQUEST}</code> in a <directive
+    module="mod_rewrite">RewriteCond</directive> — it preserves the
+    original request line exactly as the client sent it, before any
+    decoding:</p>
+
+<highlight language="config">
+# Match only the literally-encoded %2F, not a real path separator
+RewriteCond "%{THE_REQUEST}" "/horses%2F"
+RewriteRule "^/horses/ponies$" "/special-handler" [L]
+</highlight>
+
+    <p>After substitution, <module>mod_rewrite</module> re-encodes the
+    resulting URI for output. Several flags control this behavior:</p>
+
+    <ul>
+      <li><a href="flags.html#flag_b">[B]</a> — re-escape
+      backreferences so that special characters captured from the
+      decoded URI are not interpreted as delimiters in the
+      substitution.</li>
+
+      <li><a href="flags.html#flag_bnp">[BNP]</a> — when [B] is
+      active, encode spaces as <code>%20</code> rather than
+      <code>+</code> (appropriate for path components, not query
+      strings).</li>
+
+      <li><a href="flags.html#flag_ne">[NE]</a> — suppress the
+      default escaping of special characters in the substitution
+      result, allowing literal <code>#</code>, <code>?</code>, and
+      other characters to pass through unmodified on external
+      redirects.</li>
+    </ul>
+
+    <section id="allowencodedslashes">
+    <title>AllowEncodedSlashes</title>
+
+    <p>By default, Apache returns 404 for any URL containing an encoded
+    slash (<code>%2F</code>). The <directive
+    module="core">AllowEncodedSlashes</directive> directive controls
+    this behavior:</p>
+
+    <ul>
+      <li><code>Off</code> (default) — reject <code>%2F</code> with
+      404.</li>
+      <li><code>On</code> — allow <code>%2F</code> and decode it to
+      <code>/</code> before passing to handlers.</li>
+      <li><code>NoDecode</code> — allow <code>%2F</code> but keep it
+      in encoded form, letting the backend application distinguish it
+      from a real path separator.</li>
+    </ul>
+
+    <p>When using the <a href="flags.html#flag_b">[B]</a> flag with
+    URLs that may contain encoded slashes, you typically need
+    <code>AllowEncodedSlashes NoDecode</code> to prevent Apache from
+    rejecting the re-encoded result.</p>
+
+    </section>
+
+</section>
+
 <section id="InternalRuleset"><title>Ruleset Processing</title>
 
       <p>Now when <module>mod_rewrite</module> is triggered in these two API phases, it