Repository navigation
fix: parse_url handles relative references like java.net.URI #7392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -181,11 +181,16 @@ fn parse_url_component<'a>( | |
| fragment, | ||
| } = decompose_uri(value); | ||
|
|
||
| if scheme.is_none() { | ||
| if value.contains("://") { | ||
| return Err(invalid_url_err(value)); | ||
| } | ||
| return Ok(None); | ||
| // A value without a scheme is a relative reference; a leading ':' is an empty | ||
| // scheme name, which java.net.URI rejects ("Expected scheme name at index 0"). | ||
| if scheme.is_none() && value.starts_with(':') { | ||
| return Err(invalid_url_err(value)); | ||
| } | ||
|
|
||
| // java.net.URI rejects an empty authority ("//", "http://") unless a path, | ||
| // query or fragment follows it ("Expected authority"). | ||
| if authority == Some("") && path.is_empty() && query.is_none() && fragment.is_none() { | ||
| return Err(invalid_url_err(value)); | ||
| } | ||
|
|
||
| // java.net.URI rejects unbalanced brackets in authority | ||
|
|
@@ -795,4 +800,52 @@ mod tests { | |
| let result = parse_url_component("http://[::1/path", "HOST", None); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_relative_reference() -> Result<()> { | ||
| // Expected values are from Spark 4.1.1 (java.net.URI). | ||
| for (url, part, key, expected) in [ | ||
| ("/shop?ref=abc&id=42#top", "PATH", None, Some("/shop")), | ||
| ("/shop?ref=abc&id=42#top", "QUERY", Some("id"), Some("42")), | ||
| ("/shop?ref=abc&id=42#top", "REF", None, Some("top")), | ||
| ("/shop?ref=abc&id=42#top", "HOST", None, None), | ||
| ("?id=7", "FILE", None, Some("?id=7")), | ||
| ("notaurl", "PATH", None, Some("notaurl")), | ||
| ("a/b:c?id=1", "PATH", None, Some("a/b:c")), | ||
| ( | ||
| "//user:pw@example.com:8080/p", | ||
| "HOST", | ||
| None, | ||
| Some("example.com"), | ||
| ), | ||
| ( | ||
| "//user:pw@example.com:8080/p", | ||
| "USERINFO", | ||
| None, | ||
| Some("user:pw"), | ||
| ), | ||
| ("///p?id=3", "AUTHORITY", None, None), | ||
| ("//?id=4", "QUERY", None, Some("id=4")), | ||
| ("/r?to=http://x.com/a&id=1", "QUERY", Some("id"), Some("1")), | ||
| ] { | ||
| assert_eq!( | ||
| parse_url_component(url, part, key)?, | ||
| expected, | ||
| "{url} {part}" | ||
| ); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_invalid_relative_reference() { | ||
| for url in [":", ":foo", "://missing-scheme", "//", "http://"] { | ||
| for part in ["HOST", "PATH", "PROTOCOL", "FILE"] { | ||
| assert!( | ||
| parse_url_component(url, part, None).is_err(), | ||
| "{url} {part}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+841
to
+850
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -170,3 +170,33 @@ SELECT parse_url('http://host:abc/', 'AUTHORITY') | |
| -- unbalanced IPv6 bracket is invalid | ||
| query | ||
| SELECT parse_url('http://[::1/path', 'AUTHORITY') | ||
|
|
||
| -- relative references (no scheme) are parsed like java.net.URI | ||
| statement | ||
| CREATE TABLE test_parse_url_relative(url string) USING parquet | ||
|
|
||
| statement | ||
| INSERT INTO test_parse_url_relative VALUES | ||
| ('/'), | ||
| ('notaurl'), | ||
| ('a/b:c?id=1'), | ||
| ('?id=7'), | ||
| ('#frag'), | ||
| ('/p#a?id=1'), | ||
| ('/shop/item/123?ref=abc&id=42#top'), | ||
| ('/r?to=http://x.com/a&id=1'), | ||
| ('//example.com'), | ||
| ('//user:pw@example.com:8080/p?id=1#f'), | ||
| ('///p?id=3'), | ||
| ('//#f'), | ||
| ('//?id=4'), | ||
| (':foo'), | ||
| ('//'), | ||
| ('http://'), | ||
| ('http://example.com/r?to=https://y.com&id=9') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other tables in this file include a |
||
|
|
||
| query | ||
| SELECT url, parse_url(url, 'HOST'), parse_url(url, 'PATH'), parse_url(url, 'QUERY'), | ||
| parse_url(url, 'REF'), parse_url(url, 'PROTOCOL'), parse_url(url, 'FILE'), | ||
| parse_url(url, 'AUTHORITY'), parse_url(url, 'USERINFO'), parse_url(url, 'QUERY', 'id') | ||
| FROM test_parse_url_relative | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scheme.is_none() &&is redundant, sincedecompose_urionly reports a scheme when the:is at an index above 0, sovalue.starts_with(':')is enough. In the second check,fileis the path plus any?query, sofile.is_empty() && fragment.is_none()says the same thing. I read this fromdecompose_uriand have not run it.