Skip to content

Commit a19abf8

Browse files
0xPoeTurbo87
authored andcommitted
Use the same feature name validation rule from Cargo
Signed-off-by: hi-rustin <rustin.liu@gmail.com>
1 parent 4c4ba87 commit a19abf8

File tree

3 files changed

+47
-18
lines changed

3 files changed

+47
-18
lines changed

src/controllers/krate/publish.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
240240
for (key, values) in features.iter() {
241241
if !Crate::valid_feature_name(key) {
242242
return Err(cargo_err(&format!(
243-
"\"{key}\" is an invalid feature name (feature names must contain only letters, numbers, '-', '+', or '_')"
243+
"\"{key}\" is an invalid feature name (feature names must contain only Unicode XID characters, `+`, `-`, or `.` \
244+
(numbers, `+`, `-`, `_`, `.`, or most letters)"
244245
)));
245246
}
246247

src/models/krate.rs

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

223-
/// Validates the THIS parts of `features = ["THIS", "and/THIS"]`.
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 `.`.
224227
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 == '+')
228+
if name.is_empty() {
229+
return false;
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 false;
235+
}
236+
}
237+
for ch in chars {
238+
if !(unicode_xid::UnicodeXID::is_xid_continue(ch)
239+
|| ch == '+'
240+
|| ch == '-'
241+
|| ch == '.')
242+
{
243+
return false;
244+
}
245+
}
246+
247+
true
248+
}
249+
250+
/// Validates a whole feature string, `features = ["THIS", "and/THIS", "dep:THIS", "dep?/THIS"]`.
251+
pub fn valid_feature(name: &str) -> bool {
252+
if let Some((dep, dep_feat)) = name.split_once('/') {
253+
let dep = dep.strip_suffix('?').unwrap_or(dep);
254+
Crate::valid_dependency_name(dep) && Crate::valid_feature_name(dep_feat)
255+
} else if let Some((_, dep)) = name.split_once("dep:") {
256+
Crate::valid_dependency_name(dep)
257+
} else {
258+
Crate::valid_feature_name(name)
259+
}
229260
}
230261

231262
/// Validates the prefix in front of the slash: `features = ["THIS/feature"]`.
@@ -237,17 +268,6 @@ impl Crate {
237268
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
238269
}
239270

240-
/// Validates a whole feature string, `features = ["THIS", "ALL/THIS"]`.
241-
pub fn valid_feature(name: &str) -> bool {
242-
match name.split_once('/') {
243-
Some((dep, dep_feat)) => {
244-
let dep = dep.strip_suffix('?').unwrap_or(dep);
245-
Crate::valid_feature_prefix(dep) && Crate::valid_feature_name(dep_feat)
246-
}
247-
None => Crate::valid_feature_name(name.strip_prefix("dep:").unwrap_or(name)),
248-
}
249-
}
250-
251271
/// Return both the newest (most recently updated) and
252272
/// highest version (in semver order) for the current crate.
253273
pub fn top_versions(&self, conn: &mut PgConnection) -> QueryResult<TopVersions> {
@@ -517,6 +537,10 @@ mod tests {
517537

518538
#[test]
519539
fn valid_feature_names() {
540+
assert!(Crate::valid_feature("1foo"));
541+
assert!(Crate::valid_feature("_foo"));
542+
assert!(Crate::valid_feature("_foo-_+.1"));
543+
assert!(Crate::valid_feature("_foo-_+.1"));
520544
assert!(Crate::valid_feature("foo"));
521545
assert!(!Crate::valid_feature(""));
522546
assert!(!Crate::valid_feature("/"));
@@ -531,5 +555,9 @@ mod tests {
531555
assert!(!Crate::valid_feature("dep:foo?/bar"));
532556
assert!(!Crate::valid_feature("foo/?bar"));
533557
assert!(!Crate::valid_feature("foo?bar"));
558+
assert!(Crate::valid_feature("bar.web"));
559+
assert!(Crate::valid_feature("foo/bar.web"));
560+
assert!(!Crate::valid_feature("dep:0foo"));
561+
assert!(!Crate::valid_feature("0foo?/bar.web"));
534562
}
535563
}

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": "\"~foo\" is an invalid feature name (feature names must contain only Unicode XID characters, `+`, `-`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)"
99
}
1010
]
1111
}

0 commit comments

Comments
 (0)