commit 5ec27b75c8ac9411d9f0ea8eddbe8b4238d52fd7
parent 700a7116309c355f1557b841356e826b6d4c34de
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 5 Aug 2026 21:31:22 +0200
document paivana regex matching
Diffstat:
2 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/frags/paivana-httpd-manual.rst b/frags/paivana-httpd-manual.rst
@@ -361,8 +361,25 @@ upstream:
A few details worth knowing:
- The expression is matched against the URL path only, not against the
- host or the request method. A match anywhere in the path counts, so
- anchor with ``^`` and ``$`` if you mean "the whole path".
+ host or the request method.
+- The expression must match the path in its **entirety**.
+ ``paivana-httpd`` anchors it for you — it compiles ``^(`` *your
+ expression* ``)$``, so writing your own ``^`` and ``$`` is harmless
+ but redundant. What this rules out is a substring match:
+ ``/assets/`` on its own whitelists nothing, because no path is
+ exactly that; ``/assets/.*`` is what whitelists that subtree. The
+ enclosing group matters for alternations: ``a|b`` behaves as
+ "exactly ``a`` or exactly ``b``", not as "starts with ``a`` or ends
+ with ``b``".
+- Anchoring is a safety property, not a convenience. An unanchored
+ whitelist grants free access to every path merely *containing* the
+ expression, which a client can arrange deliberately: with a
+ substring rule, a request for ``/premium/article?x=/robots.txt``
+ would sail past the paywall.
+- Paths longer than 16 kb are rejected outright with HTTP ``414 URI
+ Too Long``, before any expression is evaluated. The cap bounds the
+ cost of a pathological expression on a path an unpaying client
+ controls; no legitimate URL comes close to it.
- An invalid regular expression is a fatal configuration error:
``paivana-httpd`` logs the problem and refuses to start.
- The internal endpoints ``POST /.well-known/paivana`` (payment
@@ -505,6 +522,32 @@ entry in ``choices`` describes one way the client may pay and is an
:ts:type:`OrderChoice` object (so the paywall can also support
the use of subscription tokens, discount coupons, etc.).
+Two properties of the matching are easy to get wrong, and both make
+the difference between a template that applies and one that silently
+never does:
+
+- The subject is the **absolute URL**, not the path. It is the
+ configured ``BASE_URL`` (or the scheme and ``Host`` of the request,
+ when ``BASE_URL`` is not set) followed by the path — for example
+ ``https://example.com/premium/article``. An expression written
+ against the path alone, such as ``/premium/.*``, therefore matches
+ nothing.
+- The expression must match that URL in its **entirety**.
+ ``paivana-httpd`` anchors it for you, compiling ``^(`` *your
+ expression* ``)$``, so a substring rule does not apply to the URLs
+ containing it. Writing your own ``^`` and ``$`` remains harmless.
+ The enclosing group means an alternation such as ``a|b`` is read as
+ "exactly ``a`` or exactly ``b``".
+
+So a template meant for one subtree is written either against the
+whole URL, ``https://example\.com/premium/.*``, or with a leading
+wildcard, ``.*/premium/.*``, if the same template should apply
+regardless of the host the site is served under.
+
+A URL longer than 16 kb is answered with HTTP ``414 URI Too Long``
+and no template is evaluated for it. The cap bounds the cost of a
+pathological expression on a URL an unpaying client controls.
+
A successful create returns HTTP ``204 No Content``. After
creating the template, (re)start ``paivana-httpd`` so that it
re-reads the template list:
@@ -536,7 +579,7 @@ and 50 cents (``KUDOS:0.5``) for standard articles:
"template_contract": {
"template_type": "paivana",
"summary": "Premium article on example.com",
- "website_regex": "^/premium/.*",
+ "website_regex": ".*/premium/.*",
"choices": [ { "amount": "KUDOS:2" } ]
}
}'
@@ -550,7 +593,7 @@ and 50 cents (``KUDOS:0.5``) for standard articles:
"template_contract": {
"template_type": "paivana",
"summary": "Standard article on example.com",
- "website_regex": "^/standard/.*",
+ "website_regex": ".*/standard/.*",
"choices": [ { "amount": "KUDOS:0.5" } ]
}
}'
diff --git a/manpages/paivana.conf.5.rst b/manpages/paivana.conf.5.rst
@@ -143,6 +143,11 @@ WHITELIST
Whitelisted paths are never subject to the paywall. Should
be used to whitelist resources such as images or style sheets.
Paths matched against the whitelist always start with '/'.
+ The expression must match the path in its **entirety**:
+ it is anchored at both ends before it is compiled, so
+ ``/assets/`` whitelists nothing and ``/assets/.*`` is
+ needed to whitelist that subtree. Paths longer than
+ 16 kb are rejected with HTTP 414 rather than matched.
This setting is optional.
SEE ALSO