From: "Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>, Michael Montalbo <mmontalbo@gmail.com>
Subject: [PATCH v3 0/3] t/lib-httpd: make CGI test helpers concurrency-safe
Date: Thu, 13 Aug 2026 01:05:33 +0000 [thread overview]
Message-ID: <pull.2171.v3.git.1786583137.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2171.git.1783479584.gitgitgadget@gmail.com>
The httpd tests share a handful of CGI helper scripts under t/lib-httpd. Two
of them keep state between requests in the shared HTTPD_ROOT_PATH, on the
assumption that the web server hands them one request at a time. It does
not: Apache serves requests concurrently, and a single Git operation can
open more than one request to the same endpoint at once. For example, a
partial fetch that receives a REF_DELTA against a missing promisor object
lazily fetches that base while the first response is still being served.
Under that overlap apply-one-time-script.sh fails. Two requests both pass
its "test -f one-time-script" check; one removes the marker; the other then
fails to exec it, emits an empty body, and the server answers HTTP 500. In
the field this is an occasional failure[1] of
t5616.47 tolerate server sending REF_DELTA against missing promisor objects
on the macOS CI runners, with
fatal: ... The requested URL returned error: 500 fatal: could not fetch from
promisor remote
I could not reproduce it against a live server, since the window is tiny and
timing-dependent, but the macOS CI error log names the exact failure and the
new test reproduces the helper's shell error.
http-429.sh keeps its "already returned 429 once" state with the same
non-atomic check-and-set. Its retry flow is mostly sequential, so it seems
less likely to fail, but it is the same latent race.
Each helper replaces a non-atomic "test -f" check and separate follow-up
action with a single atomic operation whose exit status decides the outcome:
apply-one-time-script.sh consumes its one-shot marker with "rm" (without
"-f"), and http-429.sh elects the first request with "mkdir".
* Patch 1 fixes apply-one-time-script.sh (the actual flake) and adds t5567,
which drives the helper directly with no web server so the overlap can be
forced deterministically.
* Patch 2 makes http-429.sh atomic.
* Patch 3 documents the atomic idioms next to where t/lib-httpd.sh installs
the CGI scripts, so the guidance is in front of anyone adding another
helper.
Changes since v2:
* Patch 1 now consumes the marker with a plain "rm" (without "-f") instead
of a rename. "rm" without "-f" already fails once the marker is gone,
which is the atomicity the helper needs. A new comment explains why the
helper discards the one-time script's stderr: a losing request can find
the marker already removed.
* Patch 3 is now specific to the lib-httpd CGI helpers and lives beside
their install site in t/lib-httpd.sh, rather than as a general section in
t/README.
* Reworded several helper comments and the patch 1 and 2 log messages for
clarity and to match the code; no behavior change.
[1]
https://github.com/gitgitgadget/git/actions/runs/28756172690/job/85263916762?pr=2169
Michael Montalbo (3):
t/lib-httpd: fix apply-one-time-script race under concurrent requests
t/lib-httpd: make http-429 first-request check atomic
t/lib-httpd: document writing concurrency-safe CGI helpers
t/lib-httpd.sh | 13 ++++
t/lib-httpd/apply-one-time-script.sh | 50 +++++++++++----
t/lib-httpd/http-429.sh | 30 +++++----
t/meson.build | 1 +
t/t5567-one-time-script.sh | 96 ++++++++++++++++++++++++++++
5 files changed, 164 insertions(+), 26 deletions(-)
create mode 100755 t/t5567-one-time-script.sh
base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2171%2Fmmontalbo%2Fmm%2Flib-httpd-cgi-safe-proto-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2171/mmontalbo/mm/lib-httpd-cgi-safe-proto-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2171
Range-diff vs v2:
1: 79b56402c0 ! 1: 862c4258e5 t/lib-httpd: fix apply-one-time-script race under concurrent requests
@@ Metadata
## Commit message ##
t/lib-httpd: fix apply-one-time-script race under concurrent requests
- apply-one-time-script.sh checks for the "one-time-script" marker, runs
- it, captures the git-http-backend response in the fixed-name files "out"
- and "out_modified", and removes the marker only after it has finished
- serving the modified response. Because the client receives the response
- body before that removal, it can start its next request while the marker
- still exists. Apache can then run this CGI for two requests at once: a
- partial fetch that receives a REF_DELTA against a missing promisor
- object lazily fetches that base while the first response is still in
- flight. The second request passes the marker check, the first request
- then removes the marker, and the second fails to exec the now-missing
- marker, emits no output, and the server answers HTTP 500:
+ apply-one-time-script.sh is a CGI helper that, when the file
+ "one-time-script" is present, runs it to rewrite the git-http-backend
+ response. If "one-time-script" generates a response that differs from
+ git-http-backend, the modified response is returned and
+ "one-time-script" is deleted. Requests after the deletion return normal
+ git-http-backend responses.
+
+ The deletion is not safe under concurrency. The helper serves the
+ modified body first and deletes "one-time-script" only afterward, so a
+ client can issue its next request while the file still exists. Apache
+ runs the CGI for both requests at once, for example when a partial fetch
+ lazily fetches a missing promisor base while the first response is still
+ in flight. Both requests find the file and try to run it; the first
+ deletes it; the second then fails to exec the now-missing file, produces
+ no output, and the server returns HTTP 500:
fatal: ... The requested URL returned error: 500
fatal: could not fetch <oid> from promisor remote
- This has been seen as a flaky failure of t5616.47 on the macOS CI
- runners.
-
- Claim the marker atomically with a rename, and only once the one-time
- script has succeeded and actually changed the response; give the scratch
- files per-request names. A request that loses the rename, or whose
- script fails or leaves the response unchanged, serves the unmodified
- body and keeps the marker for a later request. No path emits an empty
- body, so the HTTP 500 no longer occurs.
+ This is the flaky failure of t5616.47 on the macOS CI runners.
- Running the one-time script more than once is fine; the only thing to
- avoid is serving a second, racing request's modified output. Two
- requests can both find the marker and run the script before either
- renames it away, but the rename is atomic, so exactly one of them wins:
- it serves its modified body and consumes the marker. The loser's rename
- fails because the marker is already gone, so it discards the modified
- output it produced and serves the unmodified body instead. The rename,
- not running the script, is what is serialized.
+ Fix it by removing the file with "rm" only after the script has actually
+ changed the response. Because "rm" without "-f" fails once the file is
+ gone, exactly one request removes it and serves the modified body. Any
+ other request serves the unmodified body. Running the script more than
+ once is harmless; only its deletion is serialized, so exactly one
+ request's modified response is ever served. Per-request scratch file
+ names keep concurrent runs from overwriting each other, and no path
+ emits an empty response body.
- Add t5567 to lock this down. The overlap depends on timing, so a live
- httpd test such as t5616.47 (the real code path) passes almost every
- time even against the buggy helper; t5567 instead drives the helper
- directly with a fake git-http-backend and forces the overlap with FIFOs.
- Against the pre-fix helper it fails with the same shell error seen in
- the field:
+ t5616.47 exercises the real code path but, being timing-dependent,
+ passes against the buggy helper almost every time. Add t5567, which
+ drives the helper directly with a fake git-http-backend and forces the
+ overlap with FIFOs; against the pre-fix helper it fails with the same
+ shell error seen in the field:
./one-time-script: No such file or directory
@@ t/lib-httpd/apply-one-time-script.sh
- LC_ALL=C
- export LC_ALL
+#
-+# Apache can run this CGI for concurrent requests (for example a partial fetch
-+# that lazily fetches a missing object while the first response is still in
-+# flight), so the helper claims the marker atomically with a rename, and only
-+# once it has decided to modify the response. A request that loses the race
-+# finds the marker already gone and serves its response unchanged; no request
-+# is left emitting an empty body, which the server would report as HTTP 500.
-+# Scratch files are per-request ($$) so concurrent requests do not clobber each
-+# other.
++# Apache can run this CGI for several requests at the same time. For example, a
++# partial fetch lazily fetches a missing object while the first response is
++# still in flight. To stay correct, the helper removes the marker only after
++# the response changes, and only with "rm" (without "-f"). The "rm" fails for
++# every request except the one that removes the marker first. That request
++# serves the modified body. Every other request serves its response unchanged.
++# No request emits an empty body, which Apache would report as HTTP 500.
++#
++# A scratch file name includes the process ID ($$), so concurrent requests do
++# not overwrite each other's files.
+#
-+# The script may run more than once: the marker is consumed when the response
-+# actually changes (the rename after "cmp"), not when the script runs, so a
-+# request whose response is not the targeted one runs the script, sees no
-+# change, and leaves the marker for a later request. That is safe because the
++# The helper can run one-time-script more than once. It consumes the marker
++# when the response changes (the "rm" after "cmp"), not when it runs the
++# script. A request whose response is not the target runs the script, finds no
++# change, and leaves the marker for a later request. This is safe because the
+# scripts are stateless filters over the captured response.
- "$GIT_EXEC_PATH/git-http-backend" >out
@@ t/lib-httpd/apply-one-time-script.sh
+modified=out-modified.$$
+"$GIT_EXEC_PATH/git-http-backend" >"$out"
+
++# one-time-script can be gone here: a concurrent request may have consumed it
++# since the "test -f" above. Then "./one-time-script" fails, the exit status
++# selects the unmodified body, and "2>/dev/null" discards the expected
++# "no such file" message.
+if ./one-time-script "$out" 2>/dev/null >"$modified" &&
+ ! cmp -s "$out" "$modified" &&
-+ mv one-time-script one-time-script.$$ 2>/dev/null
++ rm one-time-script 2>/dev/null
+then
+ cat "$modified"
else
- "$GIT_EXEC_PATH/git-http-backend"
+ cat "$out"
fi
-+rm -f "$out" "$modified" one-time-script.$$
++rm -f "$out" "$modified"
## t/meson.build ##
@@ t/meson.build: integration_tests = [
2: 5f56f32a74 ! 2: 8ed22c02a1 t/lib-httpd: make http-429 first-request check atomic
@@ Metadata
## Commit message ##
t/lib-httpd: make http-429 first-request check atomic
- http-429.sh records "already returned 429 once" with a "test -f"
- followed by a "touch" of a shared state file. That check-then-act is not
- atomic: Apache can run this CGI for several requests at once, and two of
- them can both pass the "test -f" before either "touch"es, so both treat
- themselves as the first request. The retry flow that drives this
- endpoint is mostly sequential, so this has not been seen to fail, but
- the race is latent.
+ http-429.sh returns 429 to the first request for an endpoint and
+ forwards later ones to git-http-backend so the retry succeeds. It
+ remembers that it has already answered 429 by checking for a shared
+ state file with "test -f" and creating it with "touch".
- Decide whether this is the first request with a single atomic mkdir,
- which fails if the directory already exists, so exactly one of any
- concurrent requests is rate-limited and the rest are forwarded.
+ That "check-and-set" is not atomic. Apache runs the CGI for several
+ requests at once, so two of them can pass the "test -f" before either
+ "touch"es the file, and both then answer as the first request. The
+ retry flow is mostly sequential, so this has not been observed to fail,
+ but the race is latent. Replace the check and the "touch" with a single
+ atomic "mkdir", which fails if the directory already exists, so exactly
+ one of the concurrent requests is rate-limited and the rest are
+ forwarded.
- Skipping state for "permanent" is required for correctness, not just an
- optimization. The marker tells a later or concurrent request that a 429
- has already been served, so that it forwards to git-http-backend instead
- of rate-limiting. Since "permanent" must return 429 to every request,
- that marker must never become visible to another such request.
+ The "permanent" mode needs one extra step, for correctness rather than
+ tidiness. The marker means "429 already served, now forward", so it must
+ never be visible to a request that must itself return 429. Since
+ "permanent" returns 429 to every request, it must leave no marker. The
+ original did not manage this. It ran the "touch" unconditionally and
+ removed the file with "rm -f" in the "permanent" case, and that
+ "create-then-remove" has the same racy window: a concurrent "permanent"
+ request can see the marker before the "rm -f" and be wrongly forwarded.
+ Skipping the "mkdir" entirely for "permanent" (the "!= permanent" guard)
+ leaves no marker at all, so every "permanent" request rate-limits.
- The original did not achieve this by staying stateless: its "touch" of
- the marker ran unconditionally, and the "permanent" case removed it
- afterward with "rm -f". That create-then-remove leaves a window in which
- a concurrent "permanent" request sees the marker and is forwarded. It is
- the same class of check-then-act race this patch removes from the
- first-request check, latent for the same reason: the flow is mostly
- sequential. This version fuses the check and the mark into one atomic
- mkdir and, rather than recreate the pattern as mkdir-then-rmdir, skips
- the mkdir for "permanent" with a "!= permanent" guard. No marker is ever
- created, so there is no window and every "permanent" request
- rate-limits.
-
- There is no accompanying regression test. The check and the set are
- adjacent commands with no external step in between to synchronize on, so
- the overlap cannot be forced deterministically, only reproduced
- probabilistically; the fix is preventive.
+ There is no regression test. The check and the set are adjacent commands
+ with nothing in between to synchronize on, so the overlap cannot be
+ forced deterministically, only reproduced by chance; the fix is
+ preventive.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
## t/lib-httpd/http-429.sh ##
+@@
+ # Script to return HTTP 429 Too Many Requests responses for testing retry logic.
+ # Usage: /http_429/<test-context>/<retry-after-value>/<repo-path>
+ #
+-# The test-context is a unique identifier for each test to isolate state files.
++# The test-context is a unique identifier for each test to isolate state directories.
+ # The retry-after-value can be:
+ # - A number (e.g., "1", "2", "100") - sets Retry-After header to that many seconds
+ # - "none" - no Retry-After header
@@ t/lib-httpd/http-429.sh: repo_path="${remaining#*/}" # Get rest (repo path)
# The repo name is the first component before any "/"
repo_name="${repo_path%%/*}"
@@ t/lib-httpd/http-429.sh: repo_path="${remaining#*/}" # Get rest (repo path)
-# Use current directory (HTTPD_ROOT_PATH) for state file
-# Create a safe filename from test_context, retry_after and repo_name
-# This ensures all requests for the same test context share the same state file
-+# Use current directory (HTTPD_ROOT_PATH) for state.
-+# Create a safe name from test_context, retry_after and repo_name so that all
-+# requests for the same test context share the same state.
++# Store state in the current directory (HTTPD_ROOT_PATH). Build a safe name
++# from test_context, retry_after, and repo_name, so that all requests for one
++# test context share the same state.
safe_name=$(echo "${test_context}-${retry_after}-${repo_name}" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-')
-state_file="http-429-state-${safe_name}"
+state="http-429-state-${safe_name}"
-# Check if this is the first call (no state file exists)
-if test -f "$state_file"
-+# This endpoint returns 429 to the first request and forwards later ones to
-+# git-http-backend, so the retry succeeds. Apache can run this CGI for several
-+# requests at once, so a single atomic "mkdir" elects that first request: the
-+# one whose mkdir succeeds returns 429 and leaves the directory behind as the
-+# "already rate-limited" marker; every later request finds the directory (mkdir
-+# fails) and is forwarded.
++# This endpoint returns 429 to the first request. It forwards every later
++# request to git-http-backend, so the retry succeeds. Apache can run this CGI
++# for several requests at the same time. A single atomic "mkdir" selects the
++# first request, because only one "mkdir" succeeds. That request returns 429
++# and leaves the directory as the "already rate-limited" marker. Every later
++# "mkdir" fails, so the endpoint forwards those requests.
+#
-+# "permanent" is the exception: it must return 429 to every request and never
-+# succeed, so it skips the mkdir and records no state. A leftover directory
-+# would make its own later requests find the marker and be forwarded, which is
-+# exactly what "permanent" must not do.
++# "permanent" is the exception. It must return 429 to every request, so it
++# skips the "mkdir" and records no state. A leftover directory would let a
++# later "permanent" request find the marker. The endpoint would forward that
++# request, which "permanent" must not allow.
+if test "$retry_after" != permanent && ! mkdir "$state" 2>/dev/null
then
# Already returned 429 once, forward to git-http-backend
3: f158e1f92e ! 3: 374d148f43 t/README: document writing concurrency-safe helpers
@@ Metadata
Author: Michael Montalbo <mmontalbo@gmail.com>
## Commit message ##
- t/README: document writing concurrency-safe helpers
+ t/lib-httpd: document writing concurrency-safe CGI helpers
- The apply-one-time-script.sh and http-429.sh fixes addressed the same
- underlying problem: a test helper assuming it has exclusive access to a
- file when the web server can run it for several requests at once. The
- atomic idioms that avoid this are not specific to CGI or to HTTP, so
- document them generally, alongside the other guidance for writing tests,
- and leave a pointer from the lib-httpd helper list rather than a local
- comment. The note covers the anti-pattern (a "test -f" then a separate
- act) and the two safe operations (mkdir to elect a winner, rename to
- consume a one-shot marker), citing Git's own lockfile machinery and
- make_symlink() as precedent.
+ The apply-one-time-script.sh and http-429.sh fixes share a root cause: a
+ CGI helper assumed it had a file to itself, when Apache can run the
+ helper for several requests at once. Document the atomic idioms that
+ avoid this next to where lib-httpd.sh installs the CGI scripts, so the
+ advice is in front of anyone adding another one.
- Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
+ The note describes the anti-pattern, a "test -f" check followed by a
+ separate action, and the two atomic alternatives these helpers now use:
+
+ - "mkdir", which fails if the directory exists, to elect the first
+ request (http-429.sh); and
+ - "rm" without "-f", which fails once the file is gone, to consume a
+ one-shot marker (apply-one-time-script.sh).
- ## t/README ##
-@@ t/README: from the test harness library. At the end of the script, call
- 'test_done'.
-
-
-+Writing concurrency-safe helpers
-+--------------------------------
-+
-+Some test code runs concurrently: a test may background work with '&',
-+and the helper scripts installed for the web server (in t/lib-httpd) are
-+run once per request, so the same script can execute for several
-+requests at once. Such code cannot assume it has exclusive access to a
-+file.
-+
-+When exactly one of several concurrent processes needs to "win" a
-+decision, a single atomic filesystem operation can make it, rather than
-+a check followed by a separate action. A "test -f X" then "touch X"
-+(or "rm X") races: two processes can both pass the check before either
-+acts. Two atomic operations avoid this:
-+
-+ - "mkdir dir", which fails if the directory already exists, so that
-+ exactly one caller wins, electing a first or only request (see
-+ t/lib-httpd/http-429.sh).
-+
-+ - "mv src dst" (rename), which fails if the source is gone, so that
-+ exactly one caller consumes it, claiming a planted one-shot marker
-+ (see t/lib-httpd/apply-one-time-script.sh).
-+
-+A "$$" suffix on per-request scratch files keeps concurrent invocations
-+from clobbering each other's fixed-name files.
-+
-+This is a standard shell locking idiom, and the same reasoning behind
-+Git's own lockfile machinery, which creates its lock with O_CREAT|O_EXCL,
-+and make_symlink() in t/test-lib.sh, which uses an mkdir lock: an atomic
-+operation whose failure indicates that another process got there first.
-+
-+
- Test harness library
- --------------------
-
+ Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
## t/lib-httpd.sh ##
@@ t/lib-httpd.sh: prepare_httpd() {
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
cp "$TEST_PATH"/proxy-passwd "$HTTPD_ROOT_PATH"
-+ # The web server can run any of these CGI scripts for two requests at
-+ # once; a helper that keeps state between requests must do so with an
-+ # atomic operation. See "Writing concurrency-safe helpers" in t/README.
++ # Apache runs each of these CGI scripts once per request. Apache can run one
++ # script for several requests at the same time. A helper that keeps state
++ # between requests must update that state with one atomic operation. A check
++ # and then a separate action is not safe: two requests can both pass the
++ # check before either one acts. Test the exit status of one atomic operation
++ # instead:
++ # - "mkdir dir" fails if the directory exists, so only one request
++ # succeeds. http-429.sh selects the first request this way.
++ # - "rm marker" (without "-f") fails if the marker is gone, so only one
++ # request consumes it. apply-one-time-script.sh claims its one-shot
++ # marker this way.
++ # A scratch file name includes the process ID ($$), so concurrent requests
++ # do not overwrite each other's files.
install_script incomplete-length-upload-pack-v2-http.sh
install_script incomplete-body-upload-pack-v2-http.sh
install_script error-no-report.sh
--
gitgitgadget
next prev parent reply other threads:[~2026-08-13 1:05 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 2:59 [PATCH 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-07-08 2:59 ` [PATCH 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-07-08 19:54 ` Junio C Hamano
2026-07-09 17:26 ` Michael Montalbo
2026-07-08 2:59 ` [PATCH 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-07-08 19:58 ` Junio C Hamano
2026-07-08 20:02 ` Junio C Hamano
2026-07-09 18:10 ` Michael Montalbo
2026-07-08 2:59 ` [PATCH 3/3] t/README: document writing concurrency-safe helpers Michael Montalbo via GitGitGadget
2026-07-08 19:59 ` Junio C Hamano
2026-07-10 17:30 ` [PATCH v2 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-07-10 17:30 ` [PATCH v2 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-08-04 8:03 ` Patrick Steinhardt
2026-08-07 16:29 ` Michael Montalbo
2026-07-10 17:30 ` [PATCH v2 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-07-10 17:30 ` [PATCH v2 3/3] t/README: document writing concurrency-safe helpers Michael Montalbo via GitGitGadget
2026-08-04 8:03 ` Patrick Steinhardt
2026-08-07 16:51 ` Michael Montalbo
2026-08-10 6:06 ` Patrick Steinhardt
2026-08-02 3:02 ` [PATCH v2 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo
2026-08-03 21:55 ` Junio C Hamano
2026-08-13 1:05 ` Michael Montalbo via GitGitGadget [this message]
2026-08-13 1:05 ` [PATCH v3 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-08-13 1:05 ` [PATCH v3 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-08-13 1:05 ` [PATCH v3 3/3] t/lib-httpd: document writing concurrency-safe CGI helpers Michael Montalbo via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2171.v3.git.1786583137.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=mmontalbo@gmail.com \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox