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 + /hgt/user.bed through the symlink accept + /hgt/user.bed the resolved spelling accept + /etc/passwd refuse + /../../../etc/passwd refuse + http://example.org/a.bb (a URL is not a file path) refuse + +the other two doors, for /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 + /hgt/user.bed through the symlink refuse + /hgt/user.bed the resolved spelling accept + /etc/passwd refuse + /../../../etc/passwd refuse + http://example.org/a.bb (a URL is not a file path) refuse + +the other two doors, for /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 + /hgt/user.bed through the symlink refuse + /hgt/user.bed the resolved spelling refuse + /etc/passwd refuse + /../../../etc/passwd refuse + http://example.org/a.bb (a URL is not a file path) refuse + +the other two doors, for /hgt/user.bed + isServerUserFilePath refuse + isServerUserFileOrUrl refuse + isServerUserFilePath http://example.org/a.bb refuse + isServerUserFileOrUrl http://example.org/a.bb accept +