Skip to content

Commit a39bff6

Browse files
authored
ampersands in qualifier values must be encoded (#28)
pkg:generic/name?q=a&b=c and pkg:generic/name?q=a%26b=c have different meanings.
1 parent 97142b2 commit a39bff6

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

purl/src/format.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const PURL_PATH: &AsciiSet = &PATH.add(b'@').add(b'?').add(b'#').add(b'%');
2323
const PURL_PATH_SEGMENT: &AsciiSet = &PURL_PATH.add(b'/');
2424
// For compatibility with PURL implementations that treat qualifiers as
2525
// form-urlencoded, escape '+' as well.
26-
const PURL_QUERY: &AsciiSet = &QUERY.add(b'@').add(b'?').add(b'#').add(b'+').add(b'%');
26+
const PURL_QUALIFIER: &AsciiSet =
27+
&QUERY.add(b'@').add(b'?').add(b'#').add(b'+').add(b'%').add(b'&');
2728
const PURL_FRAGMENT: &AsciiSet = &FRAGMENT.add(b'@').add(b'?').add(b'#').add(b'%');
2829

2930
impl<T> fmt::Display for GenericPurl<T>
@@ -66,8 +67,8 @@ where
6667
f,
6768
"{}{}={}",
6869
prefix,
69-
utf8_percent_encode(k, PURL_QUERY),
70-
utf8_percent_encode(v, PURL_QUERY),
70+
utf8_percent_encode(k, PURL_QUALIFIER),
71+
utf8_percent_encode(v, PURL_QUALIFIER),
7172
)?;
7273
prefix = '&';
7374
}

purl_test/src/lib.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2612,3 +2612,68 @@ fn version_encoding() {
26122612
"Incorrect qualifiers for canonicalized PURL"
26132613
);
26142614
}
2615+
#[test]
2616+
/// ampersand in qualifier value
2617+
fn ampersand_in_qualifier_value() {
2618+
let parsed = {
2619+
assert!(
2620+
matches!(
2621+
Purl::from_str("pkg:generic/name?qualifier=v%26lue"),
2622+
Err(PackageError::UnsupportedType)
2623+
),
2624+
"Type {} is not supported",
2625+
"generic"
2626+
);
2627+
match GenericPurl::<String>::from_str("pkg:generic/name?qualifier=v%26lue") {
2628+
Ok(purl) => purl,
2629+
Err(error) => {
2630+
panic!(
2631+
"Failed to parse valid purl {:?}: {}",
2632+
"pkg:generic/name?qualifier=v%26lue", error
2633+
)
2634+
},
2635+
}
2636+
};
2637+
assert_eq!("generic", parsed.package_type(), "Incorrect package type");
2638+
assert_eq!(None, parsed.namespace(), "Incorrect namespace");
2639+
assert_eq!("name", parsed.name(), "Incorrect name");
2640+
assert_eq!(None, parsed.version(), "Incorrect version");
2641+
assert_eq!(None, parsed.subpath(), "Incorrect subpath");
2642+
assert_eq!(
2643+
[("qualifier", "v&lue")].into_iter().collect::<HashMap<&str, &str>>(),
2644+
parsed.qualifiers().iter().map(|(k, v)| (k.as_str(), v)).collect::<HashMap<&str, &str>>(),
2645+
"Incorrect qualifiers"
2646+
);
2647+
let canonicalized = parsed.to_string();
2648+
assert_eq!(
2649+
"pkg:generic/name?qualifier=v%26lue", canonicalized,
2650+
"Incorrect string representation"
2651+
);
2652+
let parsed_canonical = match GenericPurl::<String>::from_str(&canonicalized) {
2653+
Ok(purl) => purl,
2654+
Err(error) => {
2655+
panic!(
2656+
"Failed to parse valid purl {:?}: {}",
2657+
"pkg:generic/name?qualifier=v%26lue", error
2658+
)
2659+
},
2660+
};
2661+
assert_eq!(
2662+
"generic",
2663+
parsed_canonical.package_type(),
2664+
"Incorrect package type for canonicalized PURL"
2665+
);
2666+
assert_eq!(None, parsed_canonical.namespace(), "Incorrect namespace for canonicalized PURL");
2667+
assert_eq!("name", parsed_canonical.name(), "Incorrect name for canonicalized PURL");
2668+
assert_eq!(None, parsed_canonical.version(), "Incorrect version for canonicalized PURL");
2669+
assert_eq!(None, parsed_canonical.subpath(), "Incorrect subpath for canonicalized PURL");
2670+
assert_eq!(
2671+
[("qualifier", "v&lue")].into_iter().collect::<HashMap<&str, &str>>(),
2672+
parsed_canonical
2673+
.qualifiers()
2674+
.iter()
2675+
.map(|(k, v)| (k.as_str(), v))
2676+
.collect::<HashMap<&str, &str>>(),
2677+
"Incorrect qualifiers for canonicalized PURL"
2678+
);
2679+
}

xtask/src/generate_tests/phylum-test-suite-data.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,16 @@
143143
"name": "name",
144144
"version": "a#/b?/c@",
145145
"is_invalid": false
146+
},
147+
{
148+
"description": "ampersand in qualifier value",
149+
"purl": "pkg:generic/name?qualifier=v%26lue",
150+
"canonical_purl": "pkg:generic/name?qualifier=v%26lue",
151+
"type": "generic",
152+
"name": "name",
153+
"qualifiers": {
154+
"qualifier": "v&lue"
155+
},
156+
"is_invalid": false
146157
}
147158
]

0 commit comments

Comments
 (0)