@@ -117,6 +117,14 @@ macro_rules! try_from_str {
117117 } ;
118118}
119119
120+ fn validate_named_toolchain ( candidate : & str ) -> Result < & str , InvalidName > {
121+ let candidate = validate ( candidate) ?;
122+ if is_legal_named_toolchain ( candidate) {
123+ Ok ( candidate)
124+ } else {
125+ Err ( InvalidName :: ToolchainName ( candidate. into ( ) ) )
126+ }
127+ }
120128/// Common validate rules for all sorts of toolchain names
121129fn validate ( candidate : & str ) -> Result < & str , InvalidName > {
122130 if let Some ( without_plus) = candidate. strip_prefix ( '+' ) {
@@ -130,6 +138,12 @@ fn validate(candidate: &str) -> Result<&str, InvalidName> {
130138 }
131139}
132140
141+ fn is_legal_named_toolchain ( candidate : & str ) -> bool {
142+ !matches ! ( candidate, "." | ".." )
143+ && candidate
144+ . chars ( )
145+ . all ( |c| c. is_ascii_alphanumeric ( ) || matches ! ( c, '.' | '_' | '-' ) )
146+ }
133147/// A toolchain name from user input.
134148#[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
135149pub ( crate ) enum ResolvableToolchainName {
@@ -260,7 +274,7 @@ pub enum ToolchainName {
260274impl ToolchainName {
261275 /// If the string is already resolved, allow direct conversion
262276 fn validate ( candidate : & str ) -> Result < Self , InvalidName > {
263- let candidate = validate ( candidate) ?;
277+ let candidate = validate_named_toolchain ( candidate) ?;
264278 candidate
265279 . parse :: < ToolchainDesc > ( )
266280 . map ( ToolchainName :: Official )
@@ -307,8 +321,13 @@ impl ResolvableLocalToolchainName {
307321 ResolvableToolchainName :: try_from ( candidate)
308322 . map ( ResolvableLocalToolchainName :: Named )
309323 . or_else ( |_| {
310- PathBasedToolchainName :: try_from ( & PathBuf :: from ( candidate) as & Path )
311- . map ( ResolvableLocalToolchainName :: Path )
324+ let path = PathBuf :: from ( candidate) ;
325+ if candidate. contains ( '/' ) || candidate. contains ( '\\' ) {
326+ PathBasedToolchainName :: try_from ( & path as & Path )
327+ . map ( ResolvableLocalToolchainName :: Path )
328+ } else {
329+ Err ( InvalidName :: ToolchainName ( candidate. into ( ) ) )
330+ }
312331 } )
313332 }
314333}
@@ -390,6 +409,7 @@ impl CustomToolchainName {
390409 || candidate == "none"
391410 || candidate. contains ( '/' )
392411 || candidate. contains ( '\\' )
412+ || !is_legal_named_toolchain ( candidate)
393413 {
394414 Err ( InvalidName :: CustomName ( candidate. into ( ) ) )
395415 } else {
@@ -469,35 +489,6 @@ impl Deref for PathBasedToolchainName {
469489 }
470490}
471491
472- fn validate_named_toolchain ( candidate : & str ) -> Result < & str , InvalidName > {
473- let candidate = validate ( candidate) ?;
474- if is_legal_named_toolchain ( candidate) {
475- Ok ( candidate)
476- } else {
477- Err ( InvalidName :: ToolchainName ( candidate. into ( ) ) )
478- }
479- }
480-
481- /// Common validate rules for all sorts of toolchain names
482- fn validate ( candidate : & str ) -> Result < & str , InvalidName > {
483- if let Some ( without_plus) = candidate. strip_prefix ( '+' ) {
484- return Err ( InvalidName :: PlusPrefix ( without_plus. to_string ( ) ) ) ;
485- }
486- let normalized_name = candidate. trim_end_matches ( '/' ) ;
487- if normalized_name. is_empty ( ) {
488- Err ( InvalidName :: ToolchainName ( candidate. into ( ) ) )
489- } else {
490- Ok ( normalized_name)
491- }
492- }
493-
494- fn is_legal_named_toolchain ( candidate : & str ) -> bool {
495- !matches ! ( candidate, "." | ".." )
496- && candidate
497- . chars ( )
498- . all ( |c| c. is_ascii_alphanumeric ( ) || matches ! ( c, '.' | '_' | '-' ) )
499- }
500-
501492#[ cfg( test) ]
502493mod tests {
503494 use std:: str:: FromStr ;
@@ -515,13 +506,21 @@ mod tests {
515506 fn partial_toolchain_desc_regex ( ) -> String {
516507 let tuple_regex = format ! (
517508 r"(-({}))?(?:-({}))?(?:-({}))?" ,
518- LIST_ARCHS . join ( "|" ) ,
519- LIST_OSES . join ( "|" ) ,
520- LIST_ENVS . join ( "|" )
509+ regex_alternates ( LIST_ARCHS ) ,
510+ regex_alternates ( LIST_OSES ) ,
511+ regex_alternates ( LIST_ENVS )
521512 ) ;
522513 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
523514 }
524515
516+ fn regex_alternates ( values : & [ & str ] ) -> String {
517+ values
518+ . iter ( )
519+ . map ( |value| regex:: escape ( value) )
520+ . collect :: < Vec < _ > > ( )
521+ . join ( "|" )
522+ }
523+
525524 prop_compose ! {
526525 fn arb_partial_toolchain_desc( )
527526 ( s in string_regex( & partial_toolchain_desc_regex( ) ) . unwrap( ) ) -> String {
@@ -531,9 +530,8 @@ mod tests {
531530
532531 prop_compose ! {
533532 fn arb_custom_name( )
534- ( s in r"[^\\/+][^\\/]* " ) -> String {
533+ ( s in r"[A-Za-z0-9._-]+ " ) -> String {
535534 // perhaps need to filter 'none' and partial toolchains - but they won't typically be generated anyway.
536- // Also filter '+' prefix as that's reserved for +toolchain syntax.
537535 s
538536 }
539537 }
@@ -566,11 +564,18 @@ mod tests {
566564
567565 #[ test]
568566 fn test_parse_custom( name in arb_custom_name( ) ) {
567+ prop_assume!( name != "none" ) ;
568+ prop_assume!( name != "." ) ;
569+ prop_assume!( name != ".." ) ;
570+ prop_assume!( PartialToolchainDesc :: from_str( & name) . is_err( ) ) ;
569571 CustomToolchainName :: try_from( name) . unwrap( ) ;
570572 }
571573
572574 #[ test]
573575 fn test_parse_resolvable_name( name in arb_resolvable_name( ) ) {
576+ prop_assume!( name != "none" ) ;
577+ prop_assume!( name != "." ) ;
578+ prop_assume!( name != ".." ) ;
574579 ResolvableToolchainName :: try_from( name) . unwrap( ) ;
575580 }
576581
@@ -602,10 +607,10 @@ mod tests {
602607 "1.8.0-x86_64-apple-darwin" ,
603608 "1.8.0-x86_64-unknown-linux-gnu" ,
604609 "1.10.0-x86_64-unknown-linux-gnu" ,
605- "bar( baz) " ,
606- "foo#bar " ,
607- "the cake is a lie " ,
608- "this.is.not-a+semver " ,
610+ "bar. baz" ,
611+ "foo_bar " ,
612+ "stage1-local " ,
613+ "this.is.not-a_semver " ,
609614 ]
610615 . into_iter ( )
611616 . map ( |s| ToolchainName :: try_from ( s) . unwrap ( ) )
@@ -626,11 +631,11 @@ mod tests {
626631 "1.8.0-beta-x86_64-apple-darwin" ,
627632 "1.8.0-beta.2-x86_64-apple-darwin" ,
628633 // https://github.com/rust-lang/rustup/issues/3517
629- "foo#bar " ,
630- "bar( baz) " ,
631- "this.is.not-a+semver " ,
634+ "foo_bar " ,
635+ "bar. baz" ,
636+ "this.is.not-a_semver " ,
632637 // https://github.com/rust-lang/rustup/issues/3168
633- "the cake is a lie " ,
638+ "stage1-local " ,
634639 ]
635640 . into_iter ( )
636641 . map ( |s| ToolchainName :: try_from ( s) . unwrap ( ) )
@@ -640,4 +645,21 @@ mod tests {
640645
641646 assert_eq ! ( expected, v) ;
642647 }
648+
649+ #[ test]
650+ fn custom_names_reject_special_characters ( ) {
651+ for name in [
652+ "bar(baz)" ,
653+ "foo#bar" ,
654+ "the cake is a lie" ,
655+ "this.is.not-a+semver" ,
656+ "quote'toolchain" ,
657+ "." ,
658+ ".." ,
659+ ] {
660+ CustomToolchainName :: try_from ( name) . unwrap_err ( ) ;
661+ ResolvableToolchainName :: try_from ( name) . unwrap_err ( ) ;
662+ ToolchainName :: try_from ( name) . unwrap_err ( ) ;
663+ }
664+ }
643665}
0 commit comments