@@ -117,6 +117,15 @@ 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+ }
128+
120129/// Common validate rules for all sorts of toolchain names
121130fn validate ( candidate : & str ) -> Result < & str , InvalidName > {
122131 if let Some ( without_plus) = candidate. strip_prefix ( '+' ) {
@@ -130,6 +139,13 @@ fn validate(candidate: &str) -> Result<&str, InvalidName> {
130139 }
131140}
132141
142+ fn is_legal_named_toolchain ( candidate : & str ) -> bool {
143+ !matches ! ( candidate, "." | ".." )
144+ && candidate
145+ . chars ( )
146+ . all ( |c| c. is_ascii_alphanumeric ( ) || matches ! ( c, '.' | '_' | '-' ) )
147+ }
148+
133149/// A toolchain name from user input.
134150#[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
135151pub ( crate ) enum ResolvableToolchainName {
@@ -152,7 +168,7 @@ impl ResolvableToolchainName {
152168 // If candidate could be resolved, return a ready to resolve version of it.
153169 // Otherwise error.
154170 fn validate ( candidate : & str ) -> Result < ResolvableToolchainName , InvalidName > {
155- let candidate = validate ( candidate) ?;
171+ let candidate = validate_named_toolchain ( candidate) ?;
156172 candidate
157173 . parse :: < PartialToolchainDesc > ( )
158174 . map ( ResolvableToolchainName :: Official )
@@ -226,7 +242,7 @@ pub(crate) enum MaybeOfficialToolchainName {
226242
227243impl MaybeOfficialToolchainName {
228244 fn validate ( candidate : & str ) -> Result < MaybeOfficialToolchainName , InvalidName > {
229- let candidate = validate ( candidate) ?;
245+ let candidate = validate_named_toolchain ( candidate) ?;
230246 if candidate == "none" {
231247 Ok ( MaybeOfficialToolchainName :: None )
232248 } else {
@@ -262,7 +278,7 @@ pub enum ToolchainName {
262278impl ToolchainName {
263279 /// If the string is already resolved, allow direct conversion
264280 fn validate ( candidate : & str ) -> Result < Self , InvalidName > {
265- let candidate = validate ( candidate) ?;
281+ let candidate = validate_named_toolchain ( candidate) ?;
266282 candidate
267283 . parse :: < ToolchainDesc > ( )
268284 . map ( ToolchainName :: Official )
@@ -311,8 +327,13 @@ impl ResolvableLocalToolchainName {
311327 ResolvableToolchainName :: try_from ( candidate)
312328 . map ( ResolvableLocalToolchainName :: Named )
313329 . or_else ( |_| {
314- PathBasedToolchainName :: try_from ( & PathBuf :: from ( candidate) as & Path )
315- . map ( ResolvableLocalToolchainName :: Path )
330+ let path = PathBuf :: from ( candidate) ;
331+ if candidate. contains ( '/' ) || candidate. contains ( '\\' ) {
332+ PathBasedToolchainName :: try_from ( & path as & Path )
333+ . map ( ResolvableLocalToolchainName :: Path )
334+ } else {
335+ Err ( InvalidName :: ToolchainName ( candidate. into ( ) ) )
336+ }
316337 } )
317338 }
318339}
@@ -394,6 +415,7 @@ impl CustomToolchainName {
394415 || candidate == "none"
395416 || candidate. contains ( '/' )
396417 || candidate. contains ( '\\' )
418+ || !is_legal_named_toolchain ( candidate)
397419 {
398420 Err ( InvalidName :: CustomName ( candidate. into ( ) ) )
399421 } else {
@@ -490,13 +512,21 @@ mod tests {
490512 fn partial_toolchain_desc_regex ( ) -> String {
491513 let tuple_regex = format ! (
492514 r"(-({}))?(?:-({}))?(?:-({}))?" ,
493- LIST_ARCHS . join ( "|" ) ,
494- LIST_OSES . join ( "|" ) ,
495- LIST_ENVS . join ( "|" )
515+ regex_alternates ( LIST_ARCHS ) ,
516+ regex_alternates ( LIST_OSES ) ,
517+ regex_alternates ( LIST_ENVS )
496518 ) ;
497519 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
498520 }
499521
522+ fn regex_alternates ( values : & [ & str ] ) -> String {
523+ values
524+ . iter ( )
525+ . map ( |value| regex:: escape ( value) )
526+ . collect :: < Vec < _ > > ( )
527+ . join ( "|" )
528+ }
529+
500530 prop_compose ! {
501531 fn arb_partial_toolchain_desc( )
502532 ( s in string_regex( & partial_toolchain_desc_regex( ) ) . unwrap( ) ) -> String {
@@ -506,9 +536,8 @@ mod tests {
506536
507537 prop_compose ! {
508538 fn arb_custom_name( )
509- ( s in r"[^\\/+][^\\/]* " ) -> String {
539+ ( s in r"[A-Za-z0-9._-]+ " ) -> String {
510540 // perhaps need to filter 'none' and partial toolchains - but they won't typically be generated anyway.
511- // Also filter '+' prefix as that's reserved for +toolchain syntax.
512541 s
513542 }
514543 }
@@ -541,11 +570,18 @@ mod tests {
541570
542571 #[ test]
543572 fn test_parse_custom( name in arb_custom_name( ) ) {
573+ prop_assume!( name != "none" ) ;
574+ prop_assume!( name != "." ) ;
575+ prop_assume!( name != ".." ) ;
576+ prop_assume!( PartialToolchainDesc :: from_str( & name) . is_err( ) ) ;
544577 CustomToolchainName :: try_from( name) . unwrap( ) ;
545578 }
546579
547580 #[ test]
548581 fn test_parse_resolvable_name( name in arb_resolvable_name( ) ) {
582+ prop_assume!( name != "none" ) ;
583+ prop_assume!( name != "." ) ;
584+ prop_assume!( name != ".." ) ;
549585 ResolvableToolchainName :: try_from( name) . unwrap( ) ;
550586 }
551587
@@ -577,10 +613,10 @@ mod tests {
577613 "1.8.0-x86_64-apple-darwin" ,
578614 "1.8.0-x86_64-unknown-linux-gnu" ,
579615 "1.10.0-x86_64-unknown-linux-gnu" ,
580- "bar( baz) " ,
581- "foo#bar " ,
582- "the cake is a lie " ,
583- "this.is.not-a+semver " ,
616+ "bar. baz" ,
617+ "foo_bar " ,
618+ "stage1-local " ,
619+ "this.is.not-a_semver " ,
584620 ]
585621 . into_iter ( )
586622 . map ( |s| ToolchainName :: try_from ( s) . unwrap ( ) )
@@ -601,11 +637,11 @@ mod tests {
601637 "1.8.0-beta-x86_64-apple-darwin" ,
602638 "1.8.0-beta.2-x86_64-apple-darwin" ,
603639 // https://github.com/rust-lang/rustup/issues/3517
604- "foo#bar " ,
605- "bar( baz) " ,
606- "this.is.not-a+semver " ,
640+ "foo_bar " ,
641+ "bar. baz" ,
642+ "this.is.not-a_semver " ,
607643 // https://github.com/rust-lang/rustup/issues/3168
608- "the cake is a lie " ,
644+ "stage1-local " ,
609645 ]
610646 . into_iter ( )
611647 . map ( |s| ToolchainName :: try_from ( s) . unwrap ( ) )
@@ -615,4 +651,21 @@ mod tests {
615651
616652 assert_eq ! ( expected, v) ;
617653 }
654+
655+ #[ test]
656+ fn custom_names_reject_special_characters ( ) {
657+ for name in [
658+ "bar(baz)" ,
659+ "foo#bar" ,
660+ "the cake is a lie" ,
661+ "this.is.not-a+semver" ,
662+ "quote'toolchain" ,
663+ "." ,
664+ ".." ,
665+ ] {
666+ CustomToolchainName :: try_from ( name) . unwrap_err ( ) ;
667+ ResolvableToolchainName :: try_from ( name) . unwrap_err ( ) ;
668+ ToolchainName :: try_from ( name) . unwrap_err ( ) ;
669+ }
670+ }
618671}
0 commit comments