diff --git a/Cargo.lock b/Cargo.lock index 3cf71c7908..9c6d014064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4145,6 +4145,7 @@ dependencies = [ "solana-sdk", "thiserror", "tiny-bip39 0.8.0", + "uriparse", "url 2.2.0", ] @@ -6567,6 +6568,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "uriparse" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "url" version = "1.7.2" diff --git a/clap-utils/Cargo.toml b/clap-utils/Cargo.toml index 66040540a6..017908162c 100644 --- a/clap-utils/Cargo.toml +++ b/clap-utils/Cargo.toml @@ -16,6 +16,7 @@ solana-remote-wallet = { path = "../remote-wallet", version = "=1.6.6" } solana-sdk = { path = "../sdk", version = "=1.6.6" } thiserror = "1.0.21" tiny-bip39 = "0.8.0" +uriparse = "0.6.3" url = "2.1.0" chrono = "0.4" diff --git a/clap-utils/src/input_validators.rs b/clap-utils/src/input_validators.rs index bd577d5023..503bf083c2 100644 --- a/clap-utils/src/input_validators.rs +++ b/clap-utils/src/input_validators.rs @@ -1,4 +1,4 @@ -use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD}; +use crate::keypair::{parse_signer_source, SignerSource, ASK_KEYWORD}; use chrono::DateTime; use solana_sdk::{ clock::{Epoch, Slot}, @@ -108,8 +108,8 @@ pub fn is_valid_pubkey(string: T) -> Result<(), String> where T: AsRef + Display, { - match parse_keypair_path(string.as_ref()) { - KeypairUrl::Filepath(path) => is_keypair(path), + match parse_signer_source(string.as_ref()) { + SignerSource::Filepath(path) => is_keypair(path), _ => Ok(()), } } diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index 78e9d11ebb..4c920eb522 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -20,6 +20,7 @@ use solana_sdk::{ }, }; use std::{ + convert::TryFrom, error, io::{stdin, stdout, Write}, process::exit, @@ -133,7 +134,7 @@ impl DefaultSigner { } } -pub enum KeypairUrl { +pub(crate) enum SignerSource { Ask, Filepath(String), Usb(String), @@ -141,17 +142,30 @@ pub enum KeypairUrl { Pubkey(Pubkey), } -pub fn parse_keypair_path(path: &str) -> KeypairUrl { - if path == "-" { - KeypairUrl::Stdin - } else if path == ASK_KEYWORD { - KeypairUrl::Ask - } else if path.starts_with("usb://") { - KeypairUrl::Usb(path.to_string()) - } else if let Ok(pubkey) = Pubkey::from_str(path) { - KeypairUrl::Pubkey(pubkey) - } else { - KeypairUrl::Filepath(path.to_string()) +pub(crate) fn parse_signer_source>(source: S) -> SignerSource { + let source = source.as_ref(); + match uriparse::URIReference::try_from(source) { + Err(_) => SignerSource::Filepath(source.to_string()), + Ok(uri) => { + if let Some(scheme) = uri.scheme() { + let scheme = scheme.as_str().to_ascii_lowercase(); + match scheme.as_str() { + "ask" => SignerSource::Ask, + "file" => SignerSource::Filepath(uri.path().to_string()), + "usb" => SignerSource::Usb(source.to_string()), + _ => SignerSource::Filepath(source.to_string()), + } + } else { + match source { + "-" => SignerSource::Stdin, + ASK_KEYWORD => SignerSource::Ask, + _ => match Pubkey::from_str(source) { + Ok(pubkey) => SignerSource::Pubkey(pubkey), + Err(_) => SignerSource::Filepath(source.to_string()), + }, + } + } + } } } @@ -198,8 +212,8 @@ pub fn signer_from_path_with_config( wallet_manager: &mut Option>, config: &SignerFromPathConfig, ) -> Result, Box> { - match parse_keypair_path(path) { - KeypairUrl::Ask => { + match parse_signer_source(path) { + SignerSource::Ask => { let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); Ok(Box::new(keypair_from_seed_phrase( keypair_name, @@ -207,7 +221,7 @@ pub fn signer_from_path_with_config( false, )?)) } - KeypairUrl::Filepath(path) => match read_keypair_file(&path) { + SignerSource::Filepath(path) => match read_keypair_file(&path) { Err(e) => Err(std::io::Error::new( std::io::ErrorKind::Other, format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e), @@ -215,11 +229,11 @@ pub fn signer_from_path_with_config( .into()), Ok(file) => Ok(Box::new(file)), }, - KeypairUrl::Stdin => { + SignerSource::Stdin => { let mut stdin = std::io::stdin(); Ok(Box::new(read_keypair(&mut stdin)?)) } - KeypairUrl::Usb(path) => { + SignerSource::Usb(path) => { if wallet_manager.is_none() { *wallet_manager = maybe_wallet_manager()?; } @@ -234,7 +248,7 @@ pub fn signer_from_path_with_config( Err(RemoteWalletError::NoDeviceFound.into()) } } - KeypairUrl::Pubkey(pubkey) => { + SignerSource::Pubkey(pubkey) => { let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name) .as_ref() .and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners)); @@ -259,8 +273,8 @@ pub fn pubkey_from_path( keypair_name: &str, wallet_manager: &mut Option>, ) -> Result> { - match parse_keypair_path(path) { - KeypairUrl::Pubkey(pubkey) => Ok(pubkey), + match parse_signer_source(path) { + SignerSource::Pubkey(pubkey) => Ok(pubkey), _ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()), } } @@ -271,14 +285,14 @@ pub fn resolve_signer_from_path( keypair_name: &str, wallet_manager: &mut Option>, ) -> Result, Box> { - match parse_keypair_path(path) { - KeypairUrl::Ask => { + match parse_signer_source(path) { + SignerSource::Ask => { let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); // This method validates the seed phrase, but returns `None` because there is no path // on disk or to a device keypair_from_seed_phrase(keypair_name, skip_validation, false).map(|_| None) } - KeypairUrl::Filepath(path) => match read_keypair_file(&path) { + SignerSource::Filepath(path) => match read_keypair_file(&path) { Err(e) => Err(std::io::Error::new( std::io::ErrorKind::Other, format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e), @@ -286,13 +300,13 @@ pub fn resolve_signer_from_path( .into()), Ok(_) => Ok(Some(path.to_string())), }, - KeypairUrl::Stdin => { + SignerSource::Stdin => { let mut stdin = std::io::stdin(); // This method validates the keypair from stdin, but returns `None` because there is no // path on disk or to a device read_keypair(&mut stdin).map(|_| None) } - KeypairUrl::Usb(path) => { + SignerSource::Usb(path) => { if wallet_manager.is_none() { *wallet_manager = maybe_wallet_manager()?; } @@ -443,4 +457,38 @@ mod tests { ]; assert_eq!(signer_pubkeys, expect); } + + #[test] + fn test_parse_signer_source() { + assert!(matches!(parse_signer_source("-"), SignerSource::Stdin)); + assert!(matches!( + parse_signer_source(ASK_KEYWORD), + SignerSource::Ask + )); + let pubkey = Pubkey::new_unique(); + assert!( + matches!(parse_signer_source(&pubkey.to_string()), SignerSource::Pubkey(p) if p == pubkey) + ); + let path = "/absolute/path".to_string(); + assert!(matches!(parse_signer_source(&path), SignerSource::Filepath(p) if p == path)); + let path = "relative/path".to_string(); + assert!(matches!(parse_signer_source(&path), SignerSource::Filepath(p) if p == path)); + let usb = "usb://ledger".to_string(); + assert!(matches!(parse_signer_source(&usb), SignerSource::Usb(u) if u == usb)); + // Catchall into SignerSource::Filepath + let junk = "sometextthatisnotapubkey".to_string(); + assert!(Pubkey::from_str(&junk).is_err()); + assert!(matches!(parse_signer_source(&junk), SignerSource::Filepath(j) if j == junk)); + + let ask = "ask:".to_string(); + assert!(matches!(parse_signer_source(&ask), SignerSource::Ask)); + let path = "/absolute/path".to_string(); + assert!( + matches!(parse_signer_source(&format!("file:{}", path)), SignerSource::Filepath(p) if p == path) + ); + let path = "relative/path".to_string(); + assert!( + matches!(parse_signer_source(&format!("file:{}", path)), SignerSource::Filepath(p) if p == path) + ); + } } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index bffb97cc80..ffd4115fdc 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -2553,7 +2553,7 @@ mod tests { } ); - // Test ResolveSigner Subcommand, KeypairUrl::Filepath + // Test ResolveSigner Subcommand, SignerSource::Filepath let test_resolve_signer = test_commands .clone() @@ -2565,7 +2565,7 @@ mod tests { signers: vec![], } ); - // Test ResolveSigner Subcommand, KeypairUrl::Pubkey (Presigner) + // Test ResolveSigner Subcommand, SignerSource::Pubkey (Presigner) let test_resolve_signer = test_commands .clone() diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 3b5b1d621f..5bb58b6fc8 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2949,6 +2949,7 @@ dependencies = [ "solana-sdk", "thiserror", "tiny-bip39", + "uriparse", "url", ] @@ -4111,6 +4112,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "uriparse" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "url" version = "2.2.1"