Skip to content

Commit ccbcaee

Browse files
committed
Fix crate and dependency name validation
Signed-off-by: hi-rustin <[email protected]>
1 parent b0f91c7 commit ccbcaee

File tree

4 files changed

+99
-41
lines changed

4 files changed

+99
-41
lines changed

src/controllers/krate/publish.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
5252
let metadata: PublishMetadata = serde_json::from_slice(&json_bytes)
5353
.map_err(|e| cargo_err(format_args!("invalid upload request: {e}")))?;
5454

55-
Crate::validate_crate_name(&metadata.name).map_err(cargo_err)?;
55+
Crate::validate_crate_name("crate", &metadata.name).map_err(cargo_err)?;
5656

5757
let version = match semver::Version::parse(&metadata.vers) {
5858
Ok(parsed) => parsed,
@@ -586,7 +586,7 @@ fn convert_dependency(
586586
}
587587

588588
pub fn validate_dependency(dep: &EncodableCrateDependency) -> AppResult<()> {
589-
Crate::validate_crate_name(&dep.name).map_err(cargo_err)?;
589+
Crate::validate_crate_name("dependency", &dep.name).map_err(cargo_err)?;
590590

591591
for feature in &dep.features {
592592
Crate::validate_feature(feature).map_err(cargo_err)?;

src/models/krate.rs

Lines changed: 95 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -192,35 +192,55 @@ impl Crate {
192192
})
193193
}
194194

195-
pub fn validate_crate_name(name: &str) -> Result<(), InvalidCrateName> {
195+
// Validates the name is a valid crate name.
196+
// This is also used for validating the name of dependencies.
197+
// So the `for_what` parameter is used to indicate what the name is used for.
198+
// It can be "crate" or "dependency".
199+
pub fn validate_crate_name(for_what: &str, name: &str) -> Result<(), InvalidCrateName> {
196200
if name.chars().count() > MAX_NAME_LENGTH {
197-
return Err(InvalidCrateName::TooLong(name.into()));
201+
return Err(InvalidCrateName::TooLong {
202+
what: for_what.into(),
203+
name: name.into(),
204+
});
198205
}
199-
Crate::validate_create_ident(name)
206+
Crate::validate_create_ident(for_what, name)
200207
}
201208

202209
// Checks that the name is a valid crate name.
203210
// 1. The name must be non-empty.
204211
// 2. The first character must be an ASCII character.
205212
// 3. The remaining characters must be ASCII alphanumerics or `-` or `_`.
206213
// Note: This differs from `valid_dependency_name`, which allows `_` as the first character.
207-
fn validate_create_ident(name: &str) -> Result<(), InvalidCrateName> {
214+
fn validate_create_ident(for_what: &str, name: &str) -> Result<(), InvalidCrateName> {
208215
if name.is_empty() {
209-
return Err(InvalidCrateName::Empty);
216+
return Err(InvalidCrateName::Empty {
217+
what: for_what.into(),
218+
});
210219
}
211220
let mut chars = name.chars();
212221
if let Some(ch) = chars.next() {
213222
if ch.is_ascii_digit() {
214-
return Err(InvalidCrateName::StartWithDigit(name.into()));
223+
return Err(InvalidCrateName::StartWithDigit {
224+
what: for_what.into(),
225+
name: name.into(),
226+
});
215227
}
216228
if !ch.is_ascii_alphabetic() {
217-
return Err(InvalidCrateName::Start(ch, name.into()));
229+
return Err(InvalidCrateName::Start {
230+
first_char: ch,
231+
what: for_what.into(),
232+
name: name.into(),
233+
});
218234
}
219235
}
220236

221237
for ch in chars {
222238
if !(ch.is_ascii_alphanumeric() || ch == '-' || ch == '_') {
223-
return Err(InvalidCrateName::Char(ch, name.into()));
239+
return Err(InvalidCrateName::Char {
240+
ch,
241+
what: for_what.into(),
242+
name: name.into(),
243+
});
224244
}
225245
}
226246

@@ -559,25 +579,33 @@ pub enum InvalidFeature {
559579

560580
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
561581
pub enum InvalidCrateName {
562-
#[error("the crate name `{0}` is too long (max {MAX_NAME_LENGTH} characters)")]
563-
TooLong(String),
564-
#[error("crate name cannot be empty")]
565-
Empty,
582+
#[error("the {what} name `{name}` is too long (max {MAX_NAME_LENGTH} characters)")]
583+
TooLong { what: String, name: String },
584+
#[error("{what} name cannot be empty")]
585+
Empty { what: String },
566586
#[error(
567-
"the name `{0}` cannot be used as a crate name, \
587+
"the name `{name}` cannot be used as a {what} name, \
568588
the name cannot start with a digit"
569589
)]
570-
StartWithDigit(String),
590+
StartWithDigit { what: String, name: String },
571591
#[error(
572-
"invalid character `{0}` in crate name: `{1}`, \
592+
"invalid character `{first_char}` in {what} name: `{name}`, \
573593
the first character must be an ASCII character"
574594
)]
575-
Start(char, String),
595+
Start {
596+
first_char: char,
597+
what: String,
598+
name: String,
599+
},
576600
#[error(
577-
"invalid character `{0}` in crate name: `{1}`, \
601+
"invalid character `{ch}` in {what} name: `{name}`, \
578602
characters must be an ASCII alphanumeric characters, `-`, or `_`"
579603
)]
580-
Char(char, String),
604+
Char {
605+
ch: char,
606+
what: String,
607+
name: String,
608+
},
581609
}
582610

583611
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
@@ -611,38 +639,68 @@ mod tests {
611639
fn validate_crate_name() {
612640
use super::{InvalidCrateName, MAX_NAME_LENGTH};
613641

614-
assert_ok!(Crate::validate_crate_name("foo"));
642+
assert_ok!(Crate::validate_crate_name("crate", "foo"));
615643
assert_err_eq!(
616-
Crate::validate_crate_name("京"),
617-
InvalidCrateName::Start('京', "京".into())
644+
Crate::validate_crate_name("crate", "京"),
645+
InvalidCrateName::Start {
646+
first_char: '京',
647+
what: "crate".into(),
648+
name: "京".into()
649+
}
618650
);
619-
assert_err_eq!(Crate::validate_crate_name(""), InvalidCrateName::Empty);
620651
assert_err_eq!(
621-
Crate::validate_crate_name("💝"),
622-
InvalidCrateName::Start('💝', "💝".into())
652+
Crate::validate_crate_name("crate", ""),
653+
InvalidCrateName::Empty {
654+
what: "crate".into()
655+
}
623656
);
624-
assert_ok!(Crate::validate_crate_name("foo_underscore"));
625-
assert_ok!(Crate::validate_crate_name("foo-dash"));
626657
assert_err_eq!(
627-
Crate::validate_crate_name("foo+plus"),
628-
InvalidCrateName::Char('+', "foo+plus".into())
658+
Crate::validate_crate_name("crate", "💝"),
659+
InvalidCrateName::Start {
660+
first_char: '💝',
661+
what: "crate".into(),
662+
name: "💝".into()
663+
}
629664
);
630-
// Crate names cannot start with an underscore on crates.io.
665+
assert_ok!(Crate::validate_crate_name("crate", "foo_underscore"));
666+
assert_ok!(Crate::validate_crate_name("crate", "foo-dash"));
631667
assert_err_eq!(
632-
Crate::validate_crate_name("_foo"),
633-
InvalidCrateName::Start('_', "_foo".into())
668+
Crate::validate_crate_name("crate", "foo+plus"),
669+
InvalidCrateName::Char {
670+
ch: '+',
671+
what: "crate".into(),
672+
name: "foo+plus".into()
673+
}
634674
);
635675
assert_err_eq!(
636-
Crate::validate_crate_name("-foo"),
637-
InvalidCrateName::Start('-', "-foo".into())
676+
Crate::validate_crate_name("crate", "_foo"),
677+
InvalidCrateName::Start {
678+
first_char: '_',
679+
what: "crate".into(),
680+
name: "_foo".into()
681+
}
638682
);
639683
assert_err_eq!(
640-
Crate::validate_crate_name("123"),
641-
InvalidCrateName::StartWithDigit("123".into())
684+
Crate::validate_crate_name("crate", "-foo"),
685+
InvalidCrateName::Start {
686+
first_char: '-',
687+
what: "crate".into(),
688+
name: "-foo".into()
689+
}
642690
);
643691
assert_err_eq!(
644-
Crate::validate_crate_name("o".repeat(MAX_NAME_LENGTH + 1).as_str()),
645-
InvalidCrateName::TooLong("o".repeat(MAX_NAME_LENGTH + 1).as_str().into())
692+
Crate::validate_crate_name("crate", "123"),
693+
InvalidCrateName::StartWithDigit {
694+
what: "crate".into(),
695+
name: "123".into()
696+
}
697+
);
698+
assert_err_eq!(
699+
Crate::validate_crate_name("crate", "o".repeat(MAX_NAME_LENGTH + 1).as_str()),
700+
InvalidCrateName::TooLong {
701+
what: "crate".into(),
702+
name: "o".repeat(MAX_NAME_LENGTH + 1).as_str().into()
703+
}
646704
);
647705
}
648706

src/models/token/scopes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl CrateScope {
108108
}
109109

110110
let name_without_wildcard = pattern.strip_suffix('*').unwrap_or(pattern);
111-
Crate::validate_crate_name(name_without_wildcard).is_ok()
111+
Crate::validate_crate_name("crate", name_without_wildcard).is_ok()
112112
}
113113

114114
pub fn matches(&self, crate_name: &str) -> bool {

src/tests/krate/publish/snapshots/all__krate__publish__dependencies__invalid_dependency_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": "invalid character `🦀` in crate name: `🦀`, the first character must be an ASCII character"
8+
"detail": "invalid character `🦀` in dependency name: `🦀`, the first character must be an ASCII character"
99
}
1010
]
1111
}

0 commit comments

Comments
 (0)