@@ -131,7 +131,7 @@ impl ResolvableToolchainName {
131131 // If candidate could be resolved, return a ready to resolve version of it.
132132 // Otherwise error.
133133 fn validate ( candidate : & str ) -> Result < Self , InvalidName > {
134- let candidate = validate ( candidate) ?;
134+ let candidate = validate_named_toolchain ( candidate) ?;
135135 if let Ok ( desc) = PartialToolchainDesc :: from_str ( candidate) {
136136 return Ok ( Self :: Official ( desc) ) ;
137137 }
@@ -201,7 +201,7 @@ pub(crate) enum MaybeOfficialToolchainName {
201201
202202impl MaybeOfficialToolchainName {
203203 fn validate ( candidate : & str ) -> Result < Self , InvalidName > {
204- Ok ( match validate ( candidate) ? {
204+ Ok ( match validate_named_toolchain ( candidate) ? {
205205 "none" => Self :: None ,
206206 candidate => Self :: Some (
207207 PartialToolchainDesc :: from_str ( candidate)
@@ -234,7 +234,7 @@ pub enum ToolchainName {
234234impl ToolchainName {
235235 /// If the string is already resolved, allow direct conversion
236236 fn validate ( candidate : & str ) -> Result < Self , InvalidName > {
237- let candidate = validate ( candidate) ?;
237+ let candidate = validate_named_toolchain ( candidate) ?;
238238 if let Ok ( desc) = ToolchainDesc :: from_str ( candidate) {
239239 return Ok ( Self :: Official ( desc) ) ;
240240 }
@@ -285,9 +285,13 @@ impl ResolvableLocalToolchainName {
285285 return Ok ( Self :: Named ( name) ) ;
286286 }
287287
288- Ok ( Self :: Path ( PathBasedToolchainName :: try_from (
289- & PathBuf :: from ( candidate) as & Path ,
290- ) ?) )
288+ if candidate. contains ( '/' ) || candidate. contains ( '\\' ) {
289+ let path = PathBuf :: from ( candidate) ;
290+ let path = PathBasedToolchainName :: try_from ( & path as & Path ) ?;
291+ return Ok ( Self :: Path ( path) ) ;
292+ }
293+
294+ Err ( InvalidName :: ToolchainName ( candidate. into ( ) ) )
291295 }
292296}
293297
@@ -363,12 +367,9 @@ pub struct CustomToolchainName(String);
363367
364368impl CustomToolchainName {
365369 fn validate ( candidate : & str ) -> Result < Self , InvalidName > {
366- let candidate = validate ( candidate) ?;
367- if candidate. parse :: < PartialToolchainDesc > ( ) . is_ok ( )
368- || candidate == "none"
369- || candidate. contains ( '/' )
370- || candidate. contains ( '\\' )
371- {
370+ let candidate = validate_named_toolchain ( candidate)
371+ . map_err ( |_| InvalidName :: CustomName ( candidate. into ( ) ) ) ?;
372+ if candidate. parse :: < PartialToolchainDesc > ( ) . is_ok ( ) || candidate == "none" {
372373 Err ( InvalidName :: CustomName ( candidate. into ( ) ) )
373374 } else {
374375 Ok ( Self ( candidate. into ( ) ) )
@@ -447,6 +448,19 @@ impl Deref for PathBasedToolchainName {
447448 }
448449}
449450
451+ fn validate_named_toolchain ( candidate : & str ) -> Result < & str , InvalidName > {
452+ let candidate = validate ( candidate) ?;
453+ if !matches ! ( candidate, "." | ".." )
454+ && candidate
455+ . chars ( )
456+ . all ( |c| c. is_ascii_alphanumeric ( ) || matches ! ( c, '.' | '_' | '-' ) )
457+ {
458+ Ok ( candidate)
459+ } else {
460+ Err ( InvalidName :: ToolchainName ( candidate. to_owned ( ) ) )
461+ }
462+ }
463+
450464/// Common validate rules for all sorts of toolchain names
451465fn validate ( candidate : & str ) -> Result < & str , InvalidName > {
452466 if let Some ( without_plus) = candidate. strip_prefix ( '+' ) {
@@ -477,13 +491,21 @@ mod tests {
477491 fn partial_toolchain_desc_regex ( ) -> String {
478492 let tuple_regex = format ! (
479493 r"(-({}))?(?:-({}))?(?:-({}))?" ,
480- LIST_ARCHS . join ( "|" ) ,
481- LIST_OSES . join ( "|" ) ,
482- LIST_ENVS . join ( "|" )
494+ regex_alternates ( LIST_ARCHS ) ,
495+ regex_alternates ( LIST_OSES ) ,
496+ regex_alternates ( LIST_ENVS )
483497 ) ;
484498 r"(nightly|beta|stable|[0-9]{1}(\.(0|[1-9][0-9]{0,2}))(\.(0|[1-9][0-9]{0,1}))?(-beta(\.(0|[1-9][1-9]{0,1}))?)?)(-([0-9]{4}-[0-9]{2}-[0-9]{2}))?" . to_owned ( ) + & tuple_regex
485499 }
486500
501+ fn regex_alternates ( values : & [ & str ] ) -> String {
502+ values
503+ . iter ( )
504+ . map ( |value| regex:: escape ( value) )
505+ . collect :: < Vec < _ > > ( )
506+ . join ( "|" )
507+ }
508+
487509 prop_compose ! {
488510 fn arb_partial_toolchain_desc( )
489511 ( s in string_regex( & partial_toolchain_desc_regex( ) ) . unwrap( ) ) -> String {
@@ -493,9 +515,8 @@ mod tests {
493515
494516 prop_compose ! {
495517 fn arb_custom_name( )
496- ( s in r"[^\\/+][^\\/]* " ) -> String {
518+ ( s in r"[A-Za-z0-9._-]+ " ) -> String {
497519 // perhaps need to filter 'none' and partial toolchains - but they won't typically be generated anyway.
498- // Also filter '+' prefix as that's reserved for +toolchain syntax.
499520 s
500521 }
501522 }
@@ -528,11 +549,18 @@ mod tests {
528549
529550 #[ test]
530551 fn test_parse_custom( name in arb_custom_name( ) ) {
552+ prop_assume!( name != "none" ) ;
553+ prop_assume!( name != "." ) ;
554+ prop_assume!( name != ".." ) ;
555+ prop_assume!( PartialToolchainDesc :: from_str( & name) . is_err( ) ) ;
531556 CustomToolchainName :: try_from( name) . unwrap( ) ;
532557 }
533558
534559 #[ test]
535560 fn test_parse_resolvable_name( name in arb_resolvable_name( ) ) {
561+ prop_assume!( name != "none" ) ;
562+ prop_assume!( name != "." ) ;
563+ prop_assume!( name != ".." ) ;
536564 ResolvableToolchainName :: try_from( name) . unwrap( ) ;
537565 }
538566
@@ -564,10 +592,10 @@ mod tests {
564592 "1.8.0-x86_64-apple-darwin" ,
565593 "1.8.0-x86_64-unknown-linux-gnu" ,
566594 "1.10.0-x86_64-unknown-linux-gnu" ,
567- "bar( baz) " ,
568- "foo#bar " ,
569- "the cake is a lie " ,
570- "this.is.not-a+semver " ,
595+ "bar. baz" ,
596+ "foo_bar " ,
597+ "stage1-local " ,
598+ "this.is.not-a_semver " ,
571599 ]
572600 . into_iter ( )
573601 . map ( |s| ToolchainName :: try_from ( s) . unwrap ( ) )
@@ -588,11 +616,11 @@ mod tests {
588616 "1.8.0-beta-x86_64-apple-darwin" ,
589617 "1.8.0-beta.2-x86_64-apple-darwin" ,
590618 // https://github.com/rust-lang/rustup/issues/3517
591- "foo#bar " ,
592- "bar( baz) " ,
593- "this.is.not-a+semver " ,
619+ "foo_bar " ,
620+ "bar. baz" ,
621+ "this.is.not-a_semver " ,
594622 // https://github.com/rust-lang/rustup/issues/3168
595- "the cake is a lie " ,
623+ "stage1-local " ,
596624 ]
597625 . into_iter ( )
598626 . map ( |s| ToolchainName :: try_from ( s) . unwrap ( ) )
@@ -602,4 +630,21 @@ mod tests {
602630
603631 assert_eq ! ( expected, v) ;
604632 }
633+
634+ #[ test]
635+ fn custom_names_reject_special_characters ( ) {
636+ for name in [
637+ "bar(baz)" ,
638+ "foo#bar" ,
639+ "the cake is a lie" ,
640+ "this.is.not-a+semver" ,
641+ "quote'toolchain" ,
642+ "." ,
643+ ".." ,
644+ ] {
645+ CustomToolchainName :: try_from ( name) . unwrap_err ( ) ;
646+ ResolvableToolchainName :: try_from ( name) . unwrap_err ( ) ;
647+ ToolchainName :: try_from ( name) . unwrap_err ( ) ;
648+ }
649+ }
605650}
0 commit comments