Skip to content

Commit fe21803

Browse files
committed
Use the same feature name validation rule from Cargo
Signed-off-by: hi-rustin <[email protected]>
1 parent 72356e4 commit fe21803

File tree

3 files changed

+43
-15
lines changed

3 files changed

+43
-15
lines changed

src/controllers/krate/publish.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
238238
}
239239

240240
for (key, values) in features.iter() {
241-
if !Crate::valid_feature_name(key) {
242-
return Err(cargo_err(&format!(
243-
"\"{key}\" is an invalid feature name (feature names must contain only letters, numbers, '-', '+', or '_')"
244-
)));
245-
}
246-
241+
Crate::valid_feature_name(key)?;
247242
let num_features = values.len();
248243
if num_features > max_features {
249244
return Err(cargo_err(&format!(

src/models/krate.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,41 @@ impl Crate {
220220
.unwrap_or(false)
221221
}
222222

223-
/// Validates the THIS parts of `features = ["THIS", "and/THIS"]`.
224-
pub fn valid_feature_name(name: &str) -> bool {
225-
!name.is_empty()
226-
&& name
227-
.chars()
228-
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '+' || c == '.')
223+
/// Validates the THIS parts of `features = ["THIS", "and/THIS", "dep:THIS", "dep?/THIS"]`.
224+
/// 1. The name must be non-empty.
225+
/// 2. The first character must be a Unicode XID start character, `_`, or a digit.
226+
/// 3. The remaining characters must be Unicode XID characters, `_`, `+`, `-`, or `.`.
227+
pub fn valid_feature_name(name: &str) -> AppResult<()> {
228+
if name.is_empty() {
229+
return Err(cargo_err("feature cannot be an empty"));
230+
}
231+
let mut chars = name.chars();
232+
if let Some(ch) = chars.next() {
233+
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_ascii_digit()) {
234+
return Err(cargo_err(&format!(
235+
"invalid character `{}` in feature `{}`, \
236+
the first character must be a Unicode XID start character or digit \
237+
(most letters or `_` or `0` to `9`)",
238+
ch, name,
239+
)));
240+
}
241+
}
242+
for ch in chars {
243+
if !(unicode_xid::UnicodeXID::is_xid_continue(ch)
244+
|| ch == '-'
245+
|| ch == '+'
246+
|| ch == '.')
247+
{
248+
return Err(cargo_err(&format!(
249+
"invalid character `{}` in feature `{}`, \
250+
characters must be Unicode XID characters, `+`, or `.` \
251+
(numbers, `+`, `-`, `_`, `.`, or most letters)",
252+
ch, name,
253+
)));
254+
}
255+
}
256+
257+
Ok(())
229258
}
230259

231260
/// Validates the prefix in front of the slash: `features = ["THIS/feature"]`.
@@ -242,9 +271,9 @@ impl Crate {
242271
match name.split_once('/') {
243272
Some((dep, dep_feat)) => {
244273
let dep = dep.strip_suffix('?').unwrap_or(dep);
245-
Crate::valid_feature_prefix(dep) && Crate::valid_feature_name(dep_feat)
274+
Crate::valid_feature_prefix(dep) && Crate::valid_feature_name(dep_feat).is_ok()
246275
}
247-
None => Crate::valid_feature_name(name.strip_prefix("dep:").unwrap_or(name)),
276+
None => Crate::valid_feature_name(name.strip_prefix("dep:").unwrap_or(name)).is_ok(),
248277
}
249278
}
250279

@@ -518,6 +547,10 @@ mod tests {
518547
#[test]
519548
fn valid_feature_names() {
520549
assert!(Crate::valid_feature("foo"));
550+
assert!(Crate::valid_feature("1foo"));
551+
assert!(Crate::valid_feature("_foo"));
552+
assert!(Crate::valid_feature("_foo-_+.1"));
553+
assert!(Crate::valid_feature("_foo-_+.1"));
521554
assert!(!Crate::valid_feature(""));
522555
assert!(!Crate::valid_feature("/"));
523556
assert!(!Crate::valid_feature("%/%"));

src/tests/krate/publish/snapshots/all__krate__publish__features__invalid_feature_name.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"~foo\" is an invalid feature name (feature names must contain only letters, numbers, '-', '+', or '_')"
8+
"detail": "invalid character `~` in feature `~foo`, the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`)"
99
}
1010
]
1111
}

0 commit comments

Comments
 (0)