From ad7eaf8a810b4af87f4e519dce8c64a3047a0bd3 Mon Sep 17 00:00:00 2001
From: Michael Mueller <mich@elmueller.net>
Date: Thu, 14 Nov 2024 14:37:38 +0100
Subject: [PATCH 1/3] Validate toolchains passed from external are valid

---
 crates/cargo-contract/Cargo.toml        |  1 +
 crates/cargo-contract/src/cmd/verify.rs | 57 +++++++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml
index 23b998883..bc88303ca 100644
--- a/crates/cargo-contract/Cargo.toml
+++ b/crates/cargo-contract/Cargo.toml
@@ -31,6 +31,7 @@ tracing = "0.1.40"
 tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
 which = "7.0.0"
 colored = "2.1.0"
+regex = "1"
 serde_json = "1.0.117"
 serde = { version = "1.0.202", default-features = false, features = ["derive"] }
 url = { version = "2.5.3", features = ["serde"] }
diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs
index d7d9fc9b9..8ad503882 100644
--- a/crates/cargo-contract/src/cmd/verify.rs
+++ b/crates/cargo-contract/src/cmd/verify.rs
@@ -37,6 +37,7 @@ use contract_metadata::{
     ContractMetadata,
 };
 
+use regex::Regex;
 use std::{
     fs::File,
     path::PathBuf,
@@ -196,6 +197,9 @@ impl VerifyCommand {
             let rust_toolchain = contract_build::util::rust_toolchain()
                 .expect("`rustc` always has a version associated with it.");
 
+            validate_toolchain_name(&expected_rust_toolchain)?;
+            validate_toolchain_name(&rust_toolchain)?;
+
             let rustc_matches = rust_toolchain == expected_rust_toolchain;
             let mismatched_rustc = format!(
             "\nYou are trying to `verify` a contract using the `{rust_toolchain}` toolchain.\n\
@@ -325,3 +329,56 @@ impl VerificationResult {
         Ok(serde_json::to_string_pretty(self)?)
     }
 }
+
+/// Validates that the passed `toolchain` is a valid Rust toolchain.
+///
+/// # Developers Note
+///
+/// Strictly speaking Rust has not yet defined rules for legal toolchain
+/// names. See https://github.com/rust-lang/rustup/issues/4059 for more
+/// details.
+///
+/// We took a "good enough" approach and restrict valid toolchain names
+/// to established ones.
+fn validate_toolchain_name(toolchain: &str) -> Result<()> {
+    let re = Regex::new(r"^[a-zA-Z._\-0-9]+$").expect("failed creating regex");
+    if re.is_match(toolchain) {
+        return Ok(());
+    }
+    anyhow::bail!("Invalid toolchain name: {}", toolchain)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn valid_toolchain_names() {
+        assert!(validate_toolchain_name("nightly").is_ok());
+        assert!(validate_toolchain_name("stable").is_ok());
+        assert!(validate_toolchain_name("beta").is_ok());
+
+        assert!(validate_toolchain_name("nightly-2023-01-01").is_ok());
+        assert!(validate_toolchain_name("beta-2024-01-02").is_ok());
+        assert!(validate_toolchain_name("stable-2022-03-03").is_ok());
+
+        assert!(validate_toolchain_name("1.56.0").is_ok());
+        assert!(validate_toolchain_name("1.70").is_ok());
+
+        assert!(validate_toolchain_name("1.70-aarch64-apple-darwin").is_ok());
+        assert!(
+            validate_toolchain_name("nightly-2024-11-05-aarch64-apple-darwin").is_ok()
+        );
+        assert!(validate_toolchain_name("stable-x86_64-unknown-linux-gnu").is_ok());
+    }
+
+    #[test]
+    fn invalid_toolchain_names() {
+        assert!(validate_toolchain_name("https://sh.rust-toolchain.rs").is_err());
+        assert!(validate_toolchain_name("_ $").is_err());
+        assert!(validate_toolchain_name(
+            "nightly', please install https://sh.rust-toolchain.rs"
+        )
+        .is_err());
+    }
+}

From 7ae697944b311756cf54af6832c1e96269aed0f5 Mon Sep 17 00:00:00 2001
From: Michael Mueller <mich@elmueller.net>
Date: Thu, 14 Nov 2024 14:38:32 +0100
Subject: [PATCH 2/3] Improve user output formatting

---
 crates/cargo-contract/src/cmd/verify.rs | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs
index 8ad503882..cf264d4cc 100644
--- a/crates/cargo-contract/src/cmd/verify.rs
+++ b/crates/cargo-contract/src/cmd/verify.rs
@@ -202,10 +202,12 @@ impl VerifyCommand {
 
             let rustc_matches = rust_toolchain == expected_rust_toolchain;
             let mismatched_rustc = format!(
-            "\nYou are trying to `verify` a contract using the `{rust_toolchain}` toolchain.\n\
-             However, the original contract was built using `{expected_rust_toolchain}`. Please\n\
-             install the correct toolchain (`rustup install {expected_rust_toolchain}`) and\n\
-             re-run the `verify` command.",);
+                "\nYou are trying to `verify` a contract using the following toolchain:\n\
+                {rust_toolchain}\n\n\
+                However, the original contract was built using this one:\n\
+                {expected_rust_toolchain}\n\n\
+                Please install the correct toolchain and re-run the `verify` command:\n\
+                rustup install {expected_rust_toolchain}");
             anyhow::ensure!(rustc_matches, mismatched_rustc.bright_yellow());
 
             let expected_cargo_contract_version = build_info.cargo_contract_version;
@@ -218,10 +220,11 @@ impl VerifyCommand {
                 cargo_contract_version == expected_cargo_contract_version;
             let mismatched_cargo_contract = format!(
                 "\nYou are trying to `verify` a contract using `cargo-contract` version \
-            `{cargo_contract_version}`.\n\
-             However, the original contract was built using `cargo-contract` version \
-             `{expected_cargo_contract_version}`.\n\
-             Please install the matching version and re-run the `verify` command.",
+                `{cargo_contract_version}`.\n\n\
+                However, the original contract was built using `cargo-contract` version \
+                `{expected_cargo_contract_version}`.\n\n\
+                Please install the matching version and re-run the `verify` command.\n\
+                cargo install --force --locked cargo-contract --version {expected_cargo_contract_version}",
             );
             anyhow::ensure!(
                 cargo_contract_matches,

From 5e6a44e7d1f7886189d4028332a5696aa32d821f Mon Sep 17 00:00:00 2001
From: Michael Mueller <mich@elmueller.net>
Date: Thu, 14 Nov 2024 14:46:56 +0100
Subject: [PATCH 3/3] Update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0f23295d..f1ad9fee6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 - Fix "chain configuration not found" error - [#1786](https://github.com/paritytech/cargo-contract/pull/1786)
+- Validate externally passed Rust toolchain identifiers - [#1817](https://github.com/paritytech/cargo-contract/pull/1817)
 
 ## [5.0.0-alpha]