diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 0e48b1ca5ad..ad3ea76763d 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -394,6 +394,7 @@ impl Registry { Some(s) => s, None => bail!("no upload token found, please run `cargo login`"), }; + check_token(token)?; headers.append(&format!("Authorization: {}", token))?; } self.handle.http_headers(headers)?; @@ -510,3 +511,27 @@ pub fn is_url_crates_io(url: &str) -> bool { .map(|u| u.host_str() == Some("crates.io")) .unwrap_or(false) } + +/// Checks if a token is valid or malformed. +/// +/// This check is necessary to prevent sending tokens which create an invalid HTTP request. +/// It would be easier to check just for alphanumeric tokens, but we can't be sure that all +/// registries only create tokens in that format so that is as less restricted as possible. +pub fn check_token(token: &str) -> Result<()> { + if token.is_empty() { + bail!("please provide a non-empty token"); + } + if token.bytes().all(|b| { + b >= 32 // undefined in ISO-8859-1, in ASCII/ UTF-8 not-printable character + && b < 128 // utf-8: the first bit signals a multi-byte character + && b != 127 // 127 is a control character in ascii and not in ISO 8859-1 + || b == b't' // tab is also allowed (even when < 32) + }) { + Ok(()) + } else { + Err(anyhow::anyhow!( + "token contains invalid characters.\nOnly printable ISO-8859-1 characters \ + are allowed as it is sent in a HTTPS header." + )) + } +} diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 0584a771457..a94617511fa 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -898,9 +898,7 @@ pub fn registry_login( }); if let Some(tok) = new_token.as_token() { - if tok.is_empty() { - bail!("please provide a non-empty token"); - } + crates_io::check_token(tok.as_ref().expose())?; } } if ®_cfg == &new_token { diff --git a/tests/testsuite/login.rs b/tests/testsuite/login.rs index 2757e2ad4f4..19387aed57c 100644 --- a/tests/testsuite/login.rs +++ b/tests/testsuite/login.rs @@ -126,6 +126,51 @@ fn empty_login_token() { .run(); } +#[cargo_test] +fn invalid_login_token() { + let registry = RegistryBuilder::new() + .no_configure_registry() + .no_configure_token() + .build(); + setup_new_credentials(); + + let check = |stdin: &str, stderr: &str| { + cargo_process("login") + .replace_crates_io(registry.index_url()) + .with_stdout("please paste the token found on [..]/me below") + .with_stdin(stdin) + .with_stderr(stderr) + .with_status(101) + .run(); + }; + + check( + "😄", + "\ +[UPDATING] crates.io index +[ERROR] token contains invalid characters. +Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", + ); + check( + "\u{0016}", + "\ +[ERROR] token contains invalid characters. +Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", + ); + check( + "\u{0000}", + "\ +[ERROR] token contains invalid characters. +Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", + ); + check( + "你好", + "\ +[ERROR] token contains invalid characters. +Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", + ); +} + #[cargo_test] fn bad_asymmetric_token_args() { // These cases are kept brief as the implementation is covered by clap, so this is only smoke testing that we have clap configured correctly.