Return None from make_relative when the target cannot be a base - #1151
Open
youdie006 wants to merge 1 commit into
Open
Return None from make_relative when the target cannot be a base#1151youdie006 wants to merge 1 commit into
youdie006 wants to merge 1 commit into
Conversation
make_relative guards on self.cannot_be_a_base() but never on the
argument, so a cannot-be-a-base target with a matching scheme gets past
it and comes back as a relative reference that does not resolve to it:
Url::parse("foo:/a/b")?.make_relative(&Url::parse("foo:bar")?)
// Some("../ar") -- the 'b' is gone
base.join("../ar") // foo:/ar, not foo:bar
Also Some("../") for foo:, Some("a/b") for foo:a/b, Some("../?q") for
foo:?q. Each reports success and returns a value that breaks the
inverse-of-join relation the docs open with.
The cause is extract_path_filename: rfind('/') returns None for an opaque
path, unwrap_or(0) then makes the whole string the filename, and
&filename[1..] strips what it assumes is a leading slash but is a real
character. That only holds for base-able paths - true of self, which is
guarded, and never checked for the argument.
A cannot-be-a-base URL has an opaque path, so no relative reference
resolved against any base can produce it. Returning None matches the
documented contract: "If this URL can't be a base for the given URL,
None is returned."
The existing cases in error_tests are all special schemes, which can
never be cannot-be-a-base, plus one mailto pair that the self guard
already caught. The asymmetric case had no coverage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
make_relativechecksself.cannot_be_a_base()but never the argument, so a cannot-be-a-base target with a matching scheme gets past the guard and comes back as a relative reference that does not resolve to it:Measured across a few shapes, with two controls at the bottom:
foo:/a/bfoo:barSome("../ar")foo:/arfoo:/a/bfoo:Some("../")foo:/foo:/a/bfoo:a/bSome("a/b")foo:/a/a/bfoo:/a/bfoo:?qSome("../?q")foo:/?qfoo:/a/bfoo:/a/cSome("c")foo:/a/cmailto:a@…mailto:b@…NoneEach of the first four reports success and returns a value that breaks the relation the docs open with:
The caller has no way to tell: there is no error, just a
Somewhose contents are wrong.Cause
url/src/lib.rs:531-539:&filename[1..]strips what it assumes is a leading/. For an opaque pathrfind('/')returnsNone,unwrap_or(0)makesfilenamethe whole string, and the slice eats a real character. That assumption holds only for base-able paths — true ofself, which is guarded aturl/src/lib.rs:516, and never checked for the argument.The change
Extend the existing guard. A cannot-be-a-base URL has an opaque path, so no relative reference resolved against any base can produce it, and
Noneis what the documented contract already calls for:I kept it to the guard rather than reworking
extract_path_filename, since the guard alone matches the contract exactly and leaves the base-able path handling untouched.Why the tests miss it
url/tests/unit.rs:1145test_make_relativeuseshttp,https,fileandftpthroughout — special schemes, which can never be cannot-be-a-base, so the branch is unreachable from that corpus.error_testshas one non-special entry,("mailto:a@example.com", "mailto:b@example.com"), and theselfguard catches that one. The asymmetric case — base is a base, target is not — had no coverage at all.Four cases added to
error_tests.Verification
Reverting only
url/src/lib.rsand keeping the tests fails withleft: Some("../ar"),right: None.cargo test --workspacegreen, including the WPTurltestdataruns.cargo fmt --checkandcargo clippy -p url --all-targetsclean.Related
#772 reports that
make_relativestrips a leading/forhttp://…//bar. That is a different path through the same helper — thehttpcase reachesextract_path_filenamewith a base-able path — and this change does not address it. I kept the scope to the contract violation so the two can be judged separately.Disclosure: this patch was prepared with AI assistance. The reproduction, the red/green check and the suite runs above were executed against this branch; happy to adjust anything on request.