CLI: Build file URLs with as_uri instead of by hand - #2013
Conversation
populate_config prefixed "file://" onto the result of pathname2url, which already returns a leading "///" on Windows. Three slashes plus two gives file://///C:/..., an empty authority that urlopen reads as a UNC path, so every URIRequirement failed there before the file was ever opened. That covers --single-location, --yara-file, --yara-compiled-file, --strings-file, --isf and volshell's --script. pathlib's as_uri produces the same string as the current code on POSIX for every supported Python version, and the correct one on Windows, including for UNC paths. It also sidesteps the Python 3.14 rewrite of pathname2url, which gives POSIX the same leading "///" that Windows always returned. The two other places in the codebase that build file URLs, URIRequirement.location_from_file and volshell's run_script, were already correct; this was the only one assembling the scheme by hand.
|
Thanks very much for spotting and fixing this! I worry slightly that |
|
Thanks for taking a look! Good thing to check, but I think the deprecation is on a different method. In 3.14 it is The fix calls >>> pathlib.PureWindowsPath("C:/temp/x.img").as_uri()
DeprecationWarning: pathlib.PurePath.as_uri() is deprecated and scheduled for removal in
Python 3.19. Use pathlib.Path.as_uri().
'file:///C:/temp/x.img'
>>> pathlib.Path("C:/temp/x.img").as_uri() # no warning
'file:///C:/temp/x.img'
>>> pathlib.Path.as_uri is pathlib.PurePath.as_uri
FalseRunning the call under
So I do not think anything needs changing here. If you would like a guard against a future |
|
No thanks, that all looks good, thanks for double checking it! 5:) |
Summary
populate_configbuilds the file URL for everyURIRequirementby hand:On Windows
pathname2urlalready returns a leading///, so the two slashesin the f-string are added on top of three and the result is
file://///C:/.... That parses as a UNC path with an empty host, andurlopenfails before the file is ever touched.Every option built on
URIRequirementgets the broken value stored againstit, not just the image:
--single-location,--yara-file,--yara-compiled-file,--strings-file,--isfand volshell's--script.I have reproduced the resulting failure end to end on
--single-location,--yara-fileand--script.Reproducing
No memory image needed, any existing file will do:
With an image it surfaces as a misleading error, because the advice printed
underneath asks the user to check that the file exists and is readable when it
is already both:
-fon the same file works, because it takes a different route:CommandLine.runsends it toURIRequirement.location_from_file, which joinsthe scheme with
urljoinand gets it right. So the two options that--helpdescribes as equivalent behave differently, and only the shorthand works.
The fix
as_uriis the standard library's own answer to this, and it is the only oneof the three places in this codebase that build file URLs which was doing it
by hand. The other two are already correct:
URIRequirement.location_from_fileusesurljoin("file:", ...)volshell.generic.run_scriptuses"file:" + ...with a single colonos.path.abspathis kept so that the path normalisation does not change.Why
as_uriand not just dropping the two slashesDeleting the
//would be a smaller diff, but it changes the output on Linuxfrom
file:///home/user/x.dmptofile:/home/user/x.dmp. Both are openable,but the second is a visible change on the platform CI actually runs, and it
ends up in saved configs.
as_uriis byte-identical to the current output on Linux for every Pythonversion the project supports, so on that platform this commit changes nothing
at all:
as_uri/home/user/mem.dmpfile:///home/user/mem.dmp/home/user/my images/mem dump.rawfile:///home/user/my%20images/mem%20dump.raw/home/josé/café.dmpfile:///home/jos%C3%A9/caf%C3%A9.dmpOn Windows it changes as intended, and it also fixes UNC paths, which are
common enough when images live on a share:
as_uriC:\a\b.dmpfile://///C:/a/b.dmpfile:///C:/a/b.dmp\\server\share\mem.dmpfile:////server/share/mem.dmpfile://server/share/mem.dmp(the UNC row is 3.13 and 3.14; 3.12 puts six slashes there instead of four)
I ran those against real 3.12, 3.13 and 3.14 interpreters rather than
assuming, because
pathname2urlwas rewritten in 3.14. For 3.8, the versionCI uses, I read
_PosixFlavour.make_uriinstead, which is'file://' + quote_from_bytes(bytes(path)); the only thing that differs fromthe current
quote(pathname)is the quoting call, and those agree on everypath I tried, including spaces, accents and reserved characters.
A note on Python 3.14
That rewrite makes the current code wrong on Linux too.
pathname2urlis nolonger
quote()on POSIX; it now returns a leading///for absolute pathsthe same way the Windows version always did, so the current code produces
file://///home/user/mem.dmpthere as well. Leading slashes collapse onPOSIX, so it still opens, but the stored URL is no longer canonical.
as_urigives the same answer on 3.14 as it does on 3.8.
Effect on the test suite
test_volatility.runvol_pluginpasses the image path straight to--single-location, so on Windows the whole image-backed suite currentlyfails before any plugin runs. Reverting only this commit and rerunning the
same three tests:
The full suite against
win-10_19041-2025_03.dmpandwin-xp-laptop-2005-06-25.imgis green with the change, which is the firsttime I have been able to run it here without hand editing the harness:
I mentioned this in passing on #2012 as something I had worked around
locally with a hand written
file://URL. This is the cause.To check the other options rather than assume they follow, I passed the image
as an already correct
file:///URL so that it could not be the thingfailing, and ran the rest against
win-xp-laptop-2005-06-25.img. Both failon
developand work with this change:--scriptis worth calling out becauserun_scriptbuilds URLs correctlyitself, but never gets the chance: the value already has a scheme by the time
it arrives, so it is passed straight through.
Tests
Adds
test/test_cli.pycovering the three branches of the code beingtouched: a path is converted to a URL that
urlopencan actually read back,a value that already has a scheme is left alone, and a missing path still
raises
FileNotFoundError.The first of those fails on
developand passes with this change on Windows.On Linux it passes either way for Python <=3.13, since there is nothing wrong
to catch there yet, but it will catch the 3.14 behaviour described above.
Worth flagging that
.github/workflows/test.yamlinvokes pytest againstwindows.pyandlinux.pyby path, so a new file undertest/is notcollected by CI as things stand. Happy to wire it in, or to move the tests
somewhere they will run, if you would prefer either.
ruff format,ruff checkandtest/volatility3_code_analysis.pyare clean.Possible follow-up
The deeper issue is that
-fand--single-locationbuild the same value bytwo different code paths.
populate_configcould callURIRequirement.location_from_fileand have one implementation instead oftwo. I have not done it here because that function raises
ValueErrorwherethe CLI raises
FileNotFoundError, so it is a user-visible change to errorhandling rather than a bug fix. Happy to do it separately if you want it.