Skip to content
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

encode percent signs #16

Merged
merged 2 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions purl/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ const PATH: &AsciiSet = &QUERY.add(b'?').add(b'`').add(b'{').add(b'}');

// https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#how-to-build-purl-string-from-its-components
// We mostly use the standard URL rules, but the PURL spec says '@' '?' '#' must
// be escaped except when used as a separator.
const PURL_PATH: &AsciiSet = &PATH.add(b'@').add(b'?').add(b'#');
// be escaped except when used as a separator, and we do all the encoding in one
// pass so we need to include '%'.
const PURL_PATH: &AsciiSet = &PATH.add(b'@').add(b'?').add(b'#').add(b'%');
const PURL_PATH_SEGMENT: &AsciiSet = &PURL_PATH.add(b'/');
// For compatibility with PURL implementations that treat qualifiers as
// form-urlencoded, escape '+' as well.
const PURL_QUERY: &AsciiSet = &QUERY.add(b'@').add(b'?').add(b'#').add(b'+');
const PURL_FRAGMENT: &AsciiSet = &FRAGMENT.add(b'@').add(b'?').add(b'#');
const PURL_QUERY: &AsciiSet = &QUERY.add(b'@').add(b'?').add(b'#').add(b'+').add(b'%');
const PURL_FRAGMENT: &AsciiSet = &FRAGMENT.add(b'@').add(b'?').add(b'#').add(b'%');

impl<T> fmt::Display for GenericPurl<T>
where
Expand Down
36 changes: 36 additions & 0 deletions purl_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1260,3 +1260,39 @@ fn plus_signs_and_spaces() {
"Incorrect string representation"
);
}
#[test]
/// unsupported: percent signs are properly encoded and decoded
fn unsupported_percent_signs_are_properly_encoded_and_decoded() {
assert!(
matches!(Purl::from_str("pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25"),
Err(PackageError::UnsupportedType)), "Type {} is not supported", "generic"
);
let parsed = match GenericPurl::<String>::from_str(
"pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25",
) {
Ok(purl) => purl,
Err(error) => {
panic!(
"Failed to parse valid purl {:?}: {}",
"pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25",
error
)
},
};
assert_eq!("generic", parsed.package_type(), "Incorrect package type");
assert_eq!(Some("100%"), parsed.namespace(), "Incorrect namespace");
assert_eq!("100%", parsed.name(), "Incorrect name");
assert_eq!(Some("100%"), parsed.version(), "Incorrect version");
assert_eq!(Some("100%"), parsed.subpath(), "Incorrect subpath");
let expected_qualifiers: HashMap<&str, &str> =
[("repository_url", "https://example.com/100%25/")].into_iter().collect();
assert_eq!(
expected_qualifiers,
parsed.qualifiers().iter().map(|(k, v)| (k.as_str(), v)).collect::<HashMap<&str, &str>>()
);
assert_eq!(
"pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25",
&parsed.to_string(),
"Incorrect string representation"
);
}
14 changes: 14 additions & 0 deletions xtask/src/generate_tests/phylum-test-suite-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,19 @@
},
"subpath": null,
"is_invalid": false
},
{
"description": "percent signs are properly encoded and decoded",
"purl": "pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25",
"canonical_purl": "pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25",
"type": "generic",
"namespace": "100%",
"name": "100%",
"version": "100%",
"qualifiers": {
"repository_url": "https://example.com/100%25/"
},
"subpath": "100%",
"is_invalid": false
}
]
Loading