859bc749b0593a83e1c8cd7149f956b620a24a73
braney
  Sat Sep 19 18:14:15 2026 -0700
trashDirTester: pin which file paths the cart accepts

hg/lib/cart.c screens every cart variable that names a server-made file through
these functions, so they decide whether a saved session still works.  Both ways
of being wrong are invisible: a path wrongly refused brings the session back
without its custom track or its region list and says nothing, and a path
wrongly accepted says nothing either.

That is what #38303 cost.  A check shipped in v503 discarded the saved region
list from 583 sessions on the RR and 66 on euro, and hgwdev, code review and
hgwbeta were all clean, because the corpus that shows it is only on the
production central.

Four rules pinned, each of which cost a bug or a review round: only the
configured directory is symlink-resolved and never the path from the cart; the
resolved spelling of that directory is accepted as well as the configured one,
since /userdata on the RR is a symlink and saved sessions hold both spellings;
only an absolute directory is resolved, because a relative one would be
resolved against the caller's working directory; and the acceptance runs one
direction only.  pathIsUnderDir's own edges are here too, including the sibling
directory that merely starts the same way and the name that begins with "..".

The fixture is a directory, a symlink to it and three confs naming the same
place three ways, since hgConfig caches what it read and one process can only
answer for one spelling.  Nothing machine-specific reaches the output.

Watched to fail and then pass: with the pre-#38303 code, which did not resolve
at all, the resolved spelling comes back refused and the diff is that one line.
Recorded as sandbox-ab in utils/testRegistry.

refs #38303, refs #37623, refs #38391

diff --git src/hg/lib/tests/expected/trashDirTest src/hg/lib/tests/expected/trashDirTest
new file mode 100644
index 00000000000..2748f695bca
--- /dev/null
+++ src/hg/lib/tests/expected/trashDirTest
@@ -0,0 +1,105 @@
+======== sessionDataDir spelled the symlink, absolute
+pathIsUnderDir
+  /a/b        /a/b/c.bed                                     accept
+  /a/b/       /a/b/c.bed  (trailing slash on the dir)        accept
+  /a/b        /a/bb/c.bed (sibling that starts the same)     refuse
+  /a/b        /a/b        (the directory itself, no file)    refuse
+  /a/b        /a/b/       (nothing after the slash)          refuse
+  /a/b        /a/b/../../etc/passwd                          refuse
+  /a/b        /a/b/x/../y.bed (a .. that stays inside)       refuse
+  /a/b        /a/b/..x/y.bed  (a name that begins with ..)   accept
+  (empty dir) /a/b/c.bed                                     refuse
+  /a/b        (empty path)                                   refuse
+  /           /etc/passwd  (a dir of just a slash)           refuse
+
+isRemoteUrl
+  http://example.org/a.bb                                    accept
+  https://example.org/a.bb                                   accept
+  ftp://example.org/a.bb                                     accept
+  file:///etc/passwd                                         refuse
+  gopher://example.org/a.bb                                  refuse
+  /etc/passwd                                                refuse
+
+isTrashOrSessionDataPath, sessionDataDir is the symlink, absolute
+  <link>/hgt/user.bed   through the symlink                  accept
+  <real>/hgt/user.bed   the resolved spelling                accept
+  /etc/passwd                                                refuse
+  <link>/../../../etc/passwd                                 refuse
+  http://example.org/a.bb  (a URL is not a file path)        refuse
+
+the other two doors, for <link>/hgt/user.bed
+  isServerUserFilePath                                       accept
+  isServerUserFileOrUrl                                      accept
+  isServerUserFilePath   http://example.org/a.bb             refuse
+  isServerUserFileOrUrl  http://example.org/a.bb             accept
+
+======== sessionDataDir spelled the resolved directory, absolute
+pathIsUnderDir
+  /a/b        /a/b/c.bed                                     accept
+  /a/b/       /a/b/c.bed  (trailing slash on the dir)        accept
+  /a/b        /a/bb/c.bed (sibling that starts the same)     refuse
+  /a/b        /a/b        (the directory itself, no file)    refuse
+  /a/b        /a/b/       (nothing after the slash)          refuse
+  /a/b        /a/b/../../etc/passwd                          refuse
+  /a/b        /a/b/x/../y.bed (a .. that stays inside)       refuse
+  /a/b        /a/b/..x/y.bed  (a name that begins with ..)   accept
+  (empty dir) /a/b/c.bed                                     refuse
+  /a/b        (empty path)                                   refuse
+  /           /etc/passwd  (a dir of just a slash)           refuse
+
+isRemoteUrl
+  http://example.org/a.bb                                    accept
+  https://example.org/a.bb                                   accept
+  ftp://example.org/a.bb                                     accept
+  file:///etc/passwd                                         refuse
+  gopher://example.org/a.bb                                  refuse
+  /etc/passwd                                                refuse
+
+isTrashOrSessionDataPath, sessionDataDir is the resolved directory, absolute
+  <link>/hgt/user.bed   through the symlink                  refuse
+  <real>/hgt/user.bed   the resolved spelling                accept
+  /etc/passwd                                                refuse
+  <link>/../../../etc/passwd                                 refuse
+  http://example.org/a.bb  (a URL is not a file path)        refuse
+
+the other two doors, for <link>/hgt/user.bed
+  isServerUserFilePath                                       refuse
+  isServerUserFileOrUrl                                      refuse
+  isServerUserFilePath   http://example.org/a.bb             refuse
+  isServerUserFileOrUrl  http://example.org/a.bb             accept
+
+======== sessionDataDir spelled the symlink, relative
+pathIsUnderDir
+  /a/b        /a/b/c.bed                                     accept
+  /a/b/       /a/b/c.bed  (trailing slash on the dir)        accept
+  /a/b        /a/bb/c.bed (sibling that starts the same)     refuse
+  /a/b        /a/b        (the directory itself, no file)    refuse
+  /a/b        /a/b/       (nothing after the slash)          refuse
+  /a/b        /a/b/../../etc/passwd                          refuse
+  /a/b        /a/b/x/../y.bed (a .. that stays inside)       refuse
+  /a/b        /a/b/..x/y.bed  (a name that begins with ..)   accept
+  (empty dir) /a/b/c.bed                                     refuse
+  /a/b        (empty path)                                   refuse
+  /           /etc/passwd  (a dir of just a slash)           refuse
+
+isRemoteUrl
+  http://example.org/a.bb                                    accept
+  https://example.org/a.bb                                   accept
+  ftp://example.org/a.bb                                     accept
+  file:///etc/passwd                                         refuse
+  gopher://example.org/a.bb                                  refuse
+  /etc/passwd                                                refuse
+
+isTrashOrSessionDataPath, sessionDataDir is the symlink, relative
+  <link>/hgt/user.bed   through the symlink                  refuse
+  <real>/hgt/user.bed   the resolved spelling                refuse
+  /etc/passwd                                                refuse
+  <link>/../../../etc/passwd                                 refuse
+  http://example.org/a.bb  (a URL is not a file path)        refuse
+
+the other two doors, for <link>/hgt/user.bed
+  isServerUserFilePath                                       refuse
+  isServerUserFileOrUrl                                      refuse
+  isServerUserFilePath   http://example.org/a.bb             refuse
+  isServerUserFileOrUrl  http://example.org/a.bb             accept
+