From 5e31fa33c3b5cf726a57a063b8e7a070869250fe Mon Sep 17 00:00:00 2001 From: nsfisis Date: Tue, 19 May 2026 21:46:01 +0900 Subject: fix(compile): fix more random compile errors Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/shirabe/src/util/auth_helper.rs | 42 ++- crates/shirabe/src/util/bitbucket.rs | 230 +++++++------- crates/shirabe/src/util/config_validator.rs | 12 +- crates/shirabe/src/util/filesystem.rs | 30 +- crates/shirabe/src/util/forgejo.rs | 23 +- crates/shirabe/src/util/git.rs | 383 ++++++++++++++---------- crates/shirabe/src/util/github.rs | 261 ++++++++-------- crates/shirabe/src/util/gitlab.rs | 151 +++++----- crates/shirabe/src/util/hg.rs | 36 ++- crates/shirabe/src/util/http/curl_downloader.rs | 60 ++-- crates/shirabe/src/util/http/proxy_manager.rs | 16 +- crates/shirabe/src/util/http/response.rs | 2 +- crates/shirabe/src/util/http_downloader.rs | 44 ++- crates/shirabe/src/util/loop.rs | 28 +- crates/shirabe/src/util/perforce.rs | 38 ++- crates/shirabe/src/util/process_executor.rs | 66 ++-- crates/shirabe/src/util/remote_filesystem.rs | 145 ++++----- crates/shirabe/src/util/svn.rs | 49 +-- crates/shirabe/src/util/sync_helper.rs | 30 +- crates/shirabe/src/util/url.rs | 110 ++++--- 20 files changed, 949 insertions(+), 807 deletions(-) (limited to 'crates/shirabe/src/util') diff --git a/crates/shirabe/src/util/auth_helper.rs b/crates/shirabe/src/util/auth_helper.rs index 384d7af..43e13ea 100644 --- a/crates/shirabe/src/util/auth_helper.rs +++ b/crates/shirabe/src/util/auth_helper.rs @@ -17,9 +17,10 @@ use crate::util::bitbucket::Bitbucket; use crate::util::github::GitHub; use crate::util::gitlab::GitLab; +#[derive(Debug)] pub struct AuthHelper { pub(crate) io: Box, - pub(crate) config: Config, + pub(crate) config: std::rc::Rc>, /// @var array Map of origins to message displayed displayed_origin_authentications: IndexMap, /// @var array Map of URLs and whether they already retried with authentication from Bitbucket @@ -40,7 +41,7 @@ pub enum StoreAuth { } impl AuthHelper { - pub fn new(io: Box, config: Config) -> Self { + pub fn new(io: Box, config: std::rc::Rc>) -> Self { Self { io, config, @@ -53,7 +54,8 @@ impl AuthHelper { pub fn store_auth(&self, origin: &str, store_auth: StoreAuth) -> Result<()> { // TODO(phase-b): config.get_auth_config_source() and ConfigSource methods are stubs let mut store: Option<()> = None; - let config_source = self.config.get_auth_config_source(); + let config = self.config.borrow(); + let config_source = config.get_auth_config_source(); if matches!(store_auth, StoreAuth::Bool(true)) { store = Some(()); } else if matches!(store_auth, StoreAuth::Prompt) { @@ -120,7 +122,7 @@ impl AuthHelper { ) -> Result { let mut store_auth: StoreAuth = StoreAuth::Bool(false); - let github_domains = self.config.get("github-domains"); + let github_domains = self.config.borrow_mut().get("github-domains"); let github_domain_list = match github_domains.as_array() { Some(arr) => arr.clone(), None => IndexMap::new(), @@ -129,7 +131,7 @@ impl AuthHelper { .values() .any(|v| v.as_string() == Some(origin)); - let gitlab_domains = self.config.get("gitlab-domains"); + let gitlab_domains = self.config.borrow_mut().get("gitlab-domains"); let gitlab_domain_list = match gitlab_domains.as_array() { Some(arr) => arr.clone(), None => IndexMap::new(), @@ -155,7 +157,7 @@ impl AuthHelper { let sso_url = git_hub_util.get_sso_url(&headers); message = format!( "GitHub API token requires SSO authorization. Authorize this token at {}\n", - sso_url, + sso_url.as_deref().unwrap_or(""), ); self.io.write_error3(&message, true, io_interface::NORMAL); if !self.io.is_interactive() { @@ -231,7 +233,7 @@ impl AuthHelper { if !git_hub_util.authorize_oauth(origin) && (!self.io.is_interactive() - || !git_hub_util.authorize_oauth_interactively(origin, &message)) + || !git_hub_util.authorize_oauth_interactively(origin, Some(&message))?) { return Err(TransportException::new( format!("Could not authenticate against {}", origin), @@ -289,8 +291,8 @@ impl AuthHelper { || !git_lab_util.authorize_oauth_interactively( scheme.as_string().unwrap_or(""), origin, - &message, - )) + Some(&message), + )?) { return Err(TransportException::new( format!("Could not authenticate against {}", origin), @@ -379,7 +381,8 @@ impl AuthHelper { )?; if !bit_bucket_util.authorize_oauth(&origin) && (!self.io.is_interactive() - || !bit_bucket_util.authorize_oauth_interactively(&origin, &message)) + || !bit_bucket_util + .authorize_oauth_interactively(&origin, Some(&message))?) { return Err(TransportException::new( format!("Could not authenticate against {}", origin), @@ -442,11 +445,8 @@ impl AuthHelper { .into()); } - self.io.write_error( - PhpMixed::String(format!( - " Authentication required ({}):", - origin, - )), + self.io.write_error3( + &format!(" Authentication required ({}):", origin,), true, io_interface::NORMAL, ); @@ -459,7 +459,7 @@ impl AuthHelper { ); // PHP: $this->config->get('store-auths') returns 'prompt'|bool // TODO(phase-b): decode the PhpMixed result into StoreAuth - store_auth = match self.config.get("store-auths") { + store_auth = match self.config.borrow_mut().get("store-auths") { PhpMixed::Bool(b) => StoreAuth::Bool(b), PhpMixed::String(ref s) if s == "prompt" => StoreAuth::Prompt, _ => StoreAuth::Bool(false), @@ -589,7 +589,7 @@ impl AuthHelper { } } else if origin == "github.com" && password == "x-oauth-basic" { // only add the access_token if it is actually a github API URL - if Preg::is_match(r"{^https?://api\.github\.com/}", url) { + if Preg::is_match(r"{^https?://api\.github\.com/}", url)? { headers.push(PhpMixed::String(format!( "Authorization: token {}", username, @@ -609,6 +609,7 @@ impl AuthHelper { PhpMixed::String(origin.to_string()), &PhpMixed::List( self.config + .borrow_mut() .get("gitlab-domains") .as_array() .map(|a| a.values().cloned().collect()) @@ -668,11 +669,8 @@ impl AuthHelper { let already_displayed = self.displayed_origin_authentications.get(origin) == Some(display_message); if !already_displayed { - self.io.write_error( - PhpMixed::String(display_message.clone()), - true, - io_interface::DEBUG, - ); + self.io + .write_error3(display_message, true, io_interface::DEBUG); self.displayed_origin_authentications .insert(origin.to_string(), display_message.clone()); } diff --git a/crates/shirabe/src/util/bitbucket.rs b/crates/shirabe/src/util/bitbucket.rs index ca9aabd..cd4a9fa 100644 --- a/crates/shirabe/src/util/bitbucket.rs +++ b/crates/shirabe/src/util/bitbucket.rs @@ -12,12 +12,16 @@ use crate::io::io_interface::IOInterface; use crate::util::http_downloader::HttpDownloader; use crate::util::process_executor::ProcessExecutor; +fn transport_error_code(err: &anyhow::Error) -> Option { + err.downcast_ref::().map(|te| te.code) +} + #[derive(Debug)] pub struct Bitbucket { io: Box, - config: Config, - process: ProcessExecutor, - http_downloader: HttpDownloader, + config: std::rc::Rc>, + process: std::rc::Rc>, + http_downloader: std::rc::Rc>, token: Option>, time: Option, } @@ -28,15 +32,23 @@ impl Bitbucket { pub fn new( io: Box, - config: Config, - process: Option, - http_downloader: Option, + config: std::rc::Rc>, + process: Option>>, + http_downloader: Option>>, time: Option, ) -> anyhow::Result { - let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); + let process = process.unwrap_or_else(|| { + std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( + io.clone_box(), + )))) + }); let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?, + None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader( + &*io, + &config, + IndexMap::new(), + )?)), }; Ok(Self { io, @@ -64,21 +76,23 @@ impl Bitbucket { return false; } - let mut output = String::new(); - if self.process.execute( - &[ - "git".to_string(), - "config".to_string(), - "bitbucket.accesstoken".to_string(), - ], - &mut output, - None, - ) == 0 + let mut output = PhpMixed::Null; + if self + .process + .borrow_mut() + .execute( + PhpMixed::from(vec!["git", "config", "bitbucket.accesstoken"]), + Some(&mut output), + None, + ) + .unwrap_or(1) + == 0 { + let output_str = output.as_string().unwrap_or("").trim().to_string(); self.io.set_authentication( origin_url.to_string(), "x-token-auth".to_string(), - Some(output.trim().to_string()), + Some(output_str), ); return true; } @@ -104,68 +118,59 @@ impl Bitbucket { Box::new(PhpMixed::Bool(false)), ); options.insert("http".to_string(), Box::new(PhpMixed::Array(http))); - let options = PhpMixed::Array(options); + let options: IndexMap = + options.into_iter().map(|(k, v)| (k, *v)).collect(); let response = match self .http_downloader - .get(Self::OAUTH2_ACCESS_TOKEN_URL, &options) + .borrow_mut() + .get(Self::OAUTH2_ACCESS_TOKEN_URL, options) { Ok(r) => r, Err(te) => { - if te.code == 400 { - self.io.write_error( - PhpMixed::String( - "Invalid OAuth consumer provided.".to_string(), - ), + let code = transport_error_code(&te).unwrap_or(0); + if code == 400 { + self.io.write_error3( + "Invalid OAuth consumer provided.", + true, + io_interface::NORMAL, + ); + self.io.write_error3( + "This can have three reasons:", + true, + io_interface::NORMAL, + ); + self.io.write_error3( + "1. You are authenticating with a bitbucket username/password combination", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String("This can have three reasons:".to_string()), + self.io.write_error3( + "2. You are using an OAuth consumer, but didn't configure a (dummy) callback url", + true, + io_interface::NORMAL, + ); + self.io.write_error3( + "3. You are using an OAuth consumer, but didn't configure it as private consumer", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "1. You are authenticating with a bitbucket username/password combination".to_string(), - ), - true, - io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::String( - "2. You are using an OAuth consumer, but didn't configure a (dummy) callback url".to_string(), - ), - true, - io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::String( - "3. You are using an OAuth consumer, but didn't configure it as private consumer".to_string(), - ), - true, - io_interface::NORMAL, - ); return Ok(false); } - if te.code == 403 || te.code == 401 { - self.io.write_error( - PhpMixed::String( - "Invalid OAuth consumer provided.".to_string(), - ), + if code == 403 || code == 401 { + self.io.write_error3( + "Invalid OAuth consumer provided.", + true, + io_interface::NORMAL, + ); + self.io.write_error3( + "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org \"", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org \"".to_string(), - ), - true, - io_interface::NORMAL, - ); return Ok(false); } - return Err(te.into()); + return Err(te); } }; @@ -204,43 +209,31 @@ impl Bitbucket { message: Option<&str>, ) -> anyhow::Result { if let Some(msg) = message { - self.io.write_error( - PhpMixed::String(msg.to_string()), - true, - io_interface::NORMAL, - ); + self.io.write_error3(msg, true, io_interface::NORMAL); } - let local_auth_config = self.config.get_local_auth_config_source(); + let config_ref = self.config.borrow(); + let local_auth_config = config_ref.get_local_auth_config_source(); let url = "https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/"; - self.io.write_error( - PhpMixed::String("Follow the instructions here:".to_string()), - true, - io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::String(url.to_string()), - true, - io_interface::NORMAL, - ); - let auth_config_source_name = self.config.get_auth_config_source().get_name(); + self.io + .write_error3("Follow the instructions here:", true, io_interface::NORMAL); + self.io.write_error3(url, true, io_interface::NORMAL); + let auth_config_source_name = config_ref.get_auth_config_source().get_name(); let local_name_prefix = local_auth_config .as_ref() .map(|c| format!("{} OR ", c.get_name())) .unwrap_or_default(); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "to create a consumer. It will be stored in \"{}\" for future use by Composer.", local_name_prefix + &auth_config_source_name - )), + ), true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "Ensure you enter a \"Callback URL\" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)".to_string(), - ), + self.io.write_error3( + "Ensure you enter a \"Callback URL\" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)", true, io_interface::NORMAL, ); @@ -262,15 +255,13 @@ impl Bitbucket { .to_string(); if consumer_key.is_empty() { - self.io.write_error( - PhpMixed::String("No consumer key given, aborting.".to_string()), + self.io.write_error3( + "No consumer key given, aborting.", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org \"".to_string(), - ), + self.io.write_error3( + "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org \"", true, io_interface::NORMAL, ); @@ -285,17 +276,13 @@ impl Bitbucket { .to_string(); if consumer_secret.is_empty() { - self.io.write_error( - PhpMixed::String( - "No consumer secret given, aborting.".to_string(), - ), + self.io.write_error3( + "No consumer secret given, aborting.", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org \"".to_string(), - ), + self.io.write_error3( + "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org \"", true, io_interface::NORMAL, ); @@ -312,10 +299,15 @@ impl Bitbucket { return Ok(false); } - let use_local = - store_in_local_auth_config && self.config.get_local_auth_config_source().is_some(); + let use_local = store_in_local_auth_config + && self + .config + .borrow() + .get_local_auth_config_source() + .is_some(); if use_local { - let mut auth_config_source = self.config.get_local_auth_config_source().unwrap(); + let mut auth_config_source = + self.config.borrow().get_local_auth_config_source().unwrap(); self.store_in_auth_config( &mut *auth_config_source, origin_url, @@ -323,7 +315,7 @@ impl Bitbucket { &consumer_secret, )?; } else { - let mut auth_config_source = self.config.get_auth_config_source(); + let mut auth_config_source = self.config.borrow().get_auth_config_source(); self.store_in_auth_config( &mut *auth_config_source, origin_url, @@ -333,11 +325,12 @@ impl Bitbucket { } self.config + .borrow() .get_auth_config_source() .remove_config_setting(&format!("http-basic.{}", origin_url))?; - self.io.write_error( - PhpMixed::String("Consumer stored successfully.".to_string()), + self.io.write_error3( + "Consumer stored successfully.", true, io_interface::NORMAL, ); @@ -371,9 +364,14 @@ impl Bitbucket { return Ok(String::new()); } - let use_local = self.config.get_local_auth_config_source().is_some(); + let use_local = self + .config + .borrow() + .get_local_auth_config_source() + .is_some(); if use_local { - let mut auth_config_source = self.config.get_local_auth_config_source().unwrap(); + let mut auth_config_source = + self.config.borrow().get_local_auth_config_source().unwrap(); self.store_in_auth_config( &mut *auth_config_source, origin_url, @@ -381,7 +379,7 @@ impl Bitbucket { consumer_secret, )?; } else { - let mut auth_config_source = self.config.get_auth_config_source(); + let mut auth_config_source = self.config.borrow().get_auth_config_source(); self.store_in_auth_config( &mut *auth_config_source, origin_url, @@ -415,6 +413,7 @@ impl Bitbucket { consumer_secret: &str, ) -> anyhow::Result<()> { self.config + .borrow() .get_config_source() .remove_config_setting(&format!("bitbucket-oauth.{}", origin_url))?; @@ -460,16 +459,19 @@ impl Bitbucket { Box::new(PhpMixed::Int(t + expires_in)), ); - self.config.get_auth_config_source().add_config_setting( - &format!("bitbucket-oauth.{}", origin_url), - PhpMixed::Array(consumer), - )?; + self.config + .borrow() + .get_auth_config_source() + .add_config_setting( + &format!("bitbucket-oauth.{}", origin_url), + PhpMixed::Array(consumer), + )?; Ok(()) } fn get_token_from_config(&mut self, origin_url: &str) -> bool { - let auth_config = self.config.get("bitbucket-oauth"); + let auth_config = self.config.borrow_mut().get("bitbucket-oauth"); let auth_map = match auth_config.as_array() { Some(m) => m.clone(), diff --git a/crates/shirabe/src/util/config_validator.rs b/crates/shirabe/src/util/config_validator.rs index 972191a..66fcadb 100644 --- a/crates/shirabe/src/util/config_validator.rs +++ b/crates/shirabe/src/util/config_validator.rs @@ -127,13 +127,17 @@ impl ConfigValidator { let spdx_license = license_validator.get_license_by_identifier(license); if let Some(spdx_license) = spdx_license { if spdx_license[3] { - if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?\+$}i", license) { + if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?\+$}i", license) + .unwrap_or(false) + { warnings.push(format!( "License \"{}\" is a deprecated SPDX license identifier, use \"{}-or-later\" instead", license, license.replace('+', "") )); - } else if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?$}i", license) { + } else if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?$}i", license) + .unwrap_or(false) + { warnings.push(format!( "License \"{}\" is a deprecated SPDX license identifier, use \"{}-only\" or \"{}-or-later\" instead", license, license, license @@ -154,7 +158,7 @@ impl ConfigValidator { } if let Some(PhpMixed::String(name)) = manifest.get("name") { - if !name.is_empty() && Preg::is_match(r"{[A-Z]}", name) { + if !name.is_empty() && Preg::is_match(r"{[A-Z]}", name).unwrap_or(false) { let suggest_name = Preg::replace( r"{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}", r"\1\3-\2\4", @@ -230,7 +234,7 @@ impl ConfigValidator { packages.extend(require_dev); for (package, version) in &packages { if let PhpMixed::String(version_str) = version.as_ref() { - if Preg::is_match(r"{#}", version_str) { + if Preg::is_match(r"{#}", version_str).unwrap_or(false) { warnings.push(format!( "The package \"{}\" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.", package diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs index ce2abf8..2ab08da 100644 --- a/crates/shirabe/src/util/filesystem.rs +++ b/crates/shirabe/src/util/filesystem.rs @@ -21,11 +21,11 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct Filesystem { - process_executor: Option, + process_executor: Option>>, } impl Filesystem { - pub fn new(executor: Option) -> Self { + pub fn new(executor: Option>>) -> Self { Self { process_executor: executor, } @@ -191,7 +191,7 @@ impl Filesystem { return Ok(Some(true)); } - if Preg::is_match("{^(?:[a-z]:)?[/\\\\]+$}i", directory, None).unwrap_or(false) { + if Preg::is_match3("{^(?:[a-z]:)?[/\\\\]+$}i", directory, None).unwrap_or(false) { return Err(RuntimeException { message: format!("Aborting an attempted deletion of {}, this was probably not intended, if it is a real use case please report it.", directory), code: 0, @@ -532,7 +532,7 @@ impl Filesystem { let mut common_path = to.clone(); while strpos(&format!("{}/", from), &format!("{}/", common_path)) != Some(0) && "/" != common_path - && !Preg::is_match("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false) + && !Preg::is_match3("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false) { common_path = strtr(&dirname(&common_path), "\\", "/"); } @@ -545,7 +545,7 @@ impl Filesystem { common_path = format!("{}/", rtrim(&common_path, "/")); let source_path_depth = substr_count(&substr(&from, strlen(&common_path) as isize, None), "/"); - let common_path_code = str_repeat("../", source_path_depth); + let common_path_code = str_repeat("../", source_path_depth as usize); // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups if !prefer_relative && "/" == common_path && source_path_depth > 1 { @@ -593,7 +593,7 @@ impl Filesystem { let mut common_path = to.clone(); while strpos(&format!("{}/", from), &format!("{}/", common_path)) != Some(0) && "/" != common_path - && !Preg::is_match("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false) + && !Preg::is_match3("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false) && "." != common_path { common_path = strtr(&dirname(&common_path), "\\", "/"); @@ -692,7 +692,7 @@ impl Filesystem { // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: let mut prefix_match: Vec = vec![]; - if Preg::is_match_strict_groups( + if Preg::is_match_strict_groups3( "{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix", &path, Some(&mut prefix_match), @@ -734,7 +734,7 @@ impl Filesystem { /// And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422 pub fn trim_trailing_slash(path: &str) -> String { let mut path = path.to_string(); - if !Preg::is_match("{^[/\\\\]+$}", &path, None).unwrap_or(false) { + if !Preg::is_match3("{^[/\\\\]+$}", &path, None).unwrap_or(false) { path = rtrim(&path, "/\\"); } @@ -746,7 +746,7 @@ impl Filesystem { // on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe // on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path if Platform::is_windows() { - return Preg::is_match( + return Preg::is_match3( "{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\\.\\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i", path, None, @@ -754,7 +754,7 @@ impl Filesystem { .unwrap_or(false); } - Preg::is_match( + Preg::is_match3( "{^(file://|/|/?[a-z]:[\\\\/]|\\.\\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i", path, None, @@ -809,12 +809,14 @@ impl Filesystem { size } - pub(crate) fn get_process(&mut self) -> &mut ProcessExecutor { + pub(crate) fn get_process(&mut self) -> std::cell::RefMut<'_, ProcessExecutor> { if self.process_executor.is_none() { - self.process_executor = Some(ProcessExecutor::new(None)); + self.process_executor = Some(std::rc::Rc::new(std::cell::RefCell::new( + ProcessExecutor::new(None), + ))); } - self.process_executor.as_mut().unwrap() + self.process_executor.as_ref().unwrap().borrow_mut() } /// delete symbolic link implementation (commonly known as "unlink()") @@ -960,7 +962,7 @@ impl Filesystem { let stat = lstat(junction); // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) - if let Some(arr) = stat.as_array() { + if let Some(arr) = stat { let mode = arr.get("mode").and_then(|v| v.as_int()).unwrap_or(0); return 0x4000 != (mode & 0xF000); } diff --git a/crates/shirabe/src/util/forgejo.rs b/crates/shirabe/src/util/forgejo.rs index 3aaff91..88df409 100644 --- a/crates/shirabe/src/util/forgejo.rs +++ b/crates/shirabe/src/util/forgejo.rs @@ -9,12 +9,16 @@ use crate::util::http_downloader::HttpDownloader; #[derive(Debug)] pub struct Forgejo { io: Box, - config: Config, - http_downloader: HttpDownloader, + config: std::rc::Rc>, + http_downloader: std::rc::Rc>, } impl Forgejo { - pub fn new(io: Box, config: Config, http_downloader: HttpDownloader) -> Self { + pub fn new( + io: Box, + config: std::rc::Rc>, + http_downloader: std::rc::Rc>, + ) -> Self { Self { io, config, @@ -39,7 +43,7 @@ impl Forgejo { io_interface::NORMAL, ); self.io.write_error3(&url, true, io_interface::NORMAL); - let local_auth_config = self.config.get_local_auth_config_source(); + let local_auth_config = self.config.borrow().get_local_auth_config_source(); self.io.write_error3( &format!( "Tokens will be stored in plain text in \"{}\" for future use by Composer.", @@ -47,7 +51,7 @@ impl Forgejo { .as_ref() .map(|s| format!("{} OR ", s.get_name())) .unwrap_or_default() - + self.config.get_auth_config_source().get_name() + + self.config.borrow().get_auth_config_source().get_name() ), true, io_interface::NORMAL, @@ -92,7 +96,7 @@ impl Forgejo { self.io .set_authentication(origin_url.to_string(), username.clone(), token.clone()); - match self.http_downloader.get( + match self.http_downloader.borrow_mut().get( &format!("https://{}/api/v1/version", origin_url), indexmap::indexmap! { "retry-auth-failure".to_string() => false.into(), @@ -117,15 +121,16 @@ impl Forgejo { } // store value in local/user config - let local_auth_config = self.config.get_local_auth_config_source(); + let local_auth_config = self.config.borrow().get_local_auth_config_source(); let auth_config_source = if store_in_local_auth_config { local_auth_config .as_ref() - .unwrap_or_else(|| self.config.get_auth_config_source()) + .unwrap_or_else(|| self.config.borrow().get_auth_config_source()) } else { - self.config.get_auth_config_source() + self.config.borrow().get_auth_config_source() }; self.config + .borrow() .get_config_source() .remove_config_setting(&format!("forgejo-token.{}", origin_url)); auth_config_source.add_config_setting( diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs index 8fe66a4..3093734 100644 --- a/crates/shirabe/src/util/git.rs +++ b/crates/shirabe/src/util/git.rs @@ -5,7 +5,7 @@ use anyhow::Result; use indexmap::IndexMap; use std::sync::Mutex; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map, array_merge_recursive, clearstatcache, count, explode, implode, in_array, is_array, @@ -28,10 +28,10 @@ use crate::util::url::Url; #[derive(Debug)] pub struct Git { pub(crate) io: Box, - pub(crate) config: Config, - pub(crate) process: ProcessExecutor, - pub(crate) filesystem: Filesystem, - pub(crate) http_downloader: Option, + pub(crate) config: std::rc::Rc>, + pub(crate) process: std::rc::Rc>, + pub(crate) filesystem: std::rc::Rc>, + pub(crate) http_downloader: Option>>, } /// @var string|false|null @@ -40,9 +40,9 @@ static VERSION: Mutex>> = Mutex::new(None); impl Git { pub fn new( io: Box, - config: Config, - process: ProcessExecutor, - fs: Filesystem, + config: std::rc::Rc>, + process: std::rc::Rc>, + fs: std::rc::Rc>, ) -> Self { Self { io, @@ -73,8 +73,8 @@ impl Git { .into()); } Some(io) => { - io.write_error( - PhpMixed::String(format!("{}", msg)), + io.write_error3( + &format!("{}", msg), true, io_interface::NORMAL, ); @@ -84,7 +84,10 @@ impl Git { Ok(()) } - pub fn set_http_downloader(&mut self, http_downloader: HttpDownloader) { + pub fn set_http_downloader( + &mut self, + http_downloader: std::rc::Rc>, + ) { self.http_downloader = Some(http_downloader); } @@ -114,7 +117,7 @@ impl Git { map.insert("%url%".to_string(), url.to_string()); map.insert( "%sanitizedUrl%".to_string(), - Preg::replace(r"{://([^@]+?):(.+?)@}", "://", url.to_string()), + Preg::replace(r"{://([^@]+?):(.+?)@}", "://", &url), ); array_map( @@ -144,8 +147,11 @@ impl Git { let mut last_command: PhpMixed = PhpMixed::String(String::new()); // Ensure we are allowed to use this URL by config - self.config - .prohibit_url_by_config(url, Some(self.io.as_ref()), &IndexMap::new())?; + self.config.borrow_mut().prohibit_url_by_config( + url, + Some(self.io.as_ref()), + &IndexMap::new(), + )?; let orig_cwd: Option = if initial_clone { cwd.map(|s| s.to_string()) @@ -184,7 +190,7 @@ impl Git { } else { cwd_string.clone() }; - status = this_process.execute(&cmd, &mut local_output, exec_cwd); + status = this_process.execute_args(&cmd, &mut local_output, exec_cwd); if collect_outputs { outputs.push(local_output); } @@ -217,36 +223,46 @@ impl Git { if !initial_clone { // capture username/password from URL if there is one and we have no auth configured yet let mut output = String::new(); - self.process.execute( + self.process.borrow_mut().execute_args( &vec!["git".to_string(), "remote".to_string(), "-v".to_string()], &mut output, cwd.map(|s| s.to_string()), ); - if let Some(m) = Preg::is_match_strict_groups( + let mut m: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3( r"{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im", &output, - ) { - let m3 = m.get(3).cloned().unwrap_or_default(); + Some(&mut m), + ) + .unwrap_or(false) + { + let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(); if !self.io.has_authentication(&m3) { self.io.set_authentication( m3.clone(), - rawurldecode(&m.get(1).cloned().unwrap_or_default()), - Some(rawurldecode(&m.get(2).cloned().unwrap_or_default())), + rawurldecode(&m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()), + Some(rawurldecode( + &m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(), + )), ); } } } - let protocols = self.config.get("github-protocols"); + let protocols = self.config.borrow_mut().get("github-protocols"); // public github, autoswitch protocols // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups - if let Some(m) = Preg::is_match_strict_groups( + let mut m: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3( &format!( "{{^(?:https?|git)://{}/(.*)}}", - Self::get_github_domains_regex(&self.config) + Self::get_github_domains_regex(&*self.config.borrow()) ), url, - ) { + Some(&mut m), + ) + .unwrap_or(false) + { let mut messages: Vec = vec![]; let protocols_list: Vec = match &protocols { PhpMixed::List(l) => l @@ -256,8 +272,8 @@ impl Git { _ => vec![], }; for protocol in &protocols_list { - let m1 = m.get(1).cloned().unwrap_or_default(); - let m2 = m.get(2).cloned().unwrap_or_default(); + let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); let proto_url = if protocol == "ssh" { format!("git@{}:{}", m1, m2) } else { @@ -266,7 +282,7 @@ impl Git { if run_commands_inline( &proto_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 @@ -276,12 +292,17 @@ impl Git { messages.push(format!( "- {}\n{}", proto_url, - Preg::replace(r"#^#m", " ", self.process.get_error_output().to_string()) + Preg::replace( + r"#^#m", + " ", + &self.process.borrow().get_error_output().to_string() + ) + .unwrap_or_default() )); if initial_clone { if let Some(ref orig) = orig_cwd { - self.filesystem.remove_directory(orig); + self.filesystem.borrow_mut().remove_directory(orig); } } } @@ -302,7 +323,7 @@ impl Git { } // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https - let protocols_list: Vec = match self.config.get("github-protocols") { + let protocols_list: Vec = match self.config.borrow_mut().get("github-protocols") { PhpMixed::List(l) => l .iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) @@ -312,7 +333,7 @@ impl Git { let bypass_ssh_for_github = Preg::is_match( &format!( "{{^git@{}:(.+?)\\.git$}}i", - Self::get_github_domains_regex(&self.config) + Self::get_github_domains_regex(&*self.config.borrow()) ), url, ) @@ -333,40 +354,43 @@ impl Git { if bypass_ssh_for_github || 0 != run_commands_inline( url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) { - let mut error_msg = self.process.get_error_output().to_string(); + let mut error_msg = self.process.borrow().get_error_output().to_string(); // private github repository without ssh key access, try https with auth // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups - let github_ssh_match = Preg::is_match_strict_groups( + let mut m: IndexMap = IndexMap::new(); + let github_matched = Preg::is_match_strict_groups3( &format!( "{{^git@{}:(.+?)\\.git$}}i", - Self::get_github_domains_regex(&self.config) + Self::get_github_domains_regex(&*self.config.borrow()) ), url, - ); - let github_https_match = Preg::is_match_strict_groups( - &format!( - "{{^https?://{}/(.*?)(?:\\.git)?$}}i", - Self::get_github_domains_regex(&self.config) - ), - url, - ); - if let Some(m) = github_ssh_match.or(github_https_match) { - let m1 = m.get(1).cloned().unwrap_or_default(); - let m2 = m.get(2).cloned().unwrap_or_default(); + Some(&mut m), + ) + .unwrap_or(false) + || Preg::is_match_strict_groups3( + &format!( + "{{^https?://{}/(.*?)(?:\\.git)?$}}i", + Self::get_github_domains_regex(&*self.config.borrow()) + ), + url, + Some(&mut m), + ) + .unwrap_or(false); + if github_matched { + let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if !self.io.has_authentication(&m1) { let mut git_hub_util = GitHub::new( - self.io.as_ref(), - &self.config, - &self.process, - self.http_downloader - .as_ref() - .unwrap_or(&HttpDownloader::default()), - ); + self.io.clone_box(), + std::rc::Rc::clone(&self.config), + Some(std::rc::Rc::clone(&self.process)), + self.http_downloader.clone(), + )?; let message = "Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos"; if !git_hub_util.authorize_oauth(&m1) && self.io.is_interactive() { @@ -396,7 +420,7 @@ impl Git { ); if run_commands_inline( &auth_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 @@ -405,27 +429,35 @@ impl Git { } credentials = vec![rawurlencode(&username), rawurlencode(&password)]; - error_msg = self.process.get_error_output().to_string(); + error_msg = self.process.borrow().get_error_output().to_string(); } - } else if let Some(m) = Preg::is_match_strict_groups( - r"{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i", - url, - ) - .or_else(|| { - Preg::is_match_strict_groups(r"{^(git)@(bitbucket\.org):(.+?\.git)$}i", url) - }) { + } else if { + let bb_matched = Preg::is_match_strict_groups3( + r"{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i", + url, + Some(&mut m), + ) + .unwrap_or(false) + || Preg::is_match_strict_groups3( + r"{^(git)@(bitbucket\.org):(.+?\.git)$}i", + url, + Some(&mut m), + ) + .unwrap_or(false); + bb_matched + } { // bitbucket either through oauth or app password, with fallback to ssh. let mut bitbucket_util = Bitbucket::new( - self.io.as_ref(), - &self.config, - &self.process, - self.http_downloader - .as_ref() - .unwrap_or(&HttpDownloader::default()), - ); + self.io.clone_box(), + std::rc::Rc::clone(&self.config), + Some(std::rc::Rc::clone(&self.process)), + self.http_downloader.clone(), + None, + )?; - let domain = m.get(2).cloned().unwrap_or_default(); - let mut repo_with_git_part = m.get(3).cloned().unwrap_or_default(); + let domain = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); + let mut repo_with_git_part = + m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(); if !str_ends_with(&repo_with_git_part, ".git") { repo_with_git_part.push_str(".git"); } @@ -476,7 +508,7 @@ impl Git { if run_commands_inline( &auth_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 @@ -523,7 +555,7 @@ impl Git { ); if run_commands_inline( &auth_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 @@ -535,17 +567,14 @@ impl Git { } // Falling back to ssh let ssh_url = format!("git@bitbucket.org:{}", repo_with_git_part); - self.io.write_error( - PhpMixed::String( - " No bitbucket authentication configured. Falling back to ssh." - .to_string(), - ), + self.io.write_error3( + " No bitbucket authentication configured. Falling back to ssh.", true, io_interface::NORMAL, ); if run_commands_inline( &ssh_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 @@ -553,39 +582,42 @@ impl Git { return Ok(()); } - error_msg = self.process.get_error_output().to_string(); - } else if let Some(m) = Preg::is_match_strict_groups( - &format!( - "{{^(git)@{}:(.+?\\.git)$}}i", - Self::get_gitlab_domains_regex(&self.config) - ), - url, - ) - .or_else(|| { - Preg::is_match_strict_groups( + error_msg = self.process.borrow().get_error_output().to_string(); + } else if { + let gl_matched = Preg::is_match_strict_groups3( &format!( - "{{^(https?)://{}/(.*)}}i", - Self::get_gitlab_domains_regex(&self.config) + "{{^(git)@{}:(.+?\\.git)$}}i", + Self::get_gitlab_domains_regex(&*self.config.borrow()) ), url, + Some(&mut m), ) - }) { - let mut m1 = m.get(1).cloned().unwrap_or_default(); - let m2 = m.get(2).cloned().unwrap_or_default(); - let m3 = m.get(3).cloned().unwrap_or_default(); + .unwrap_or(false) + || Preg::is_match_strict_groups3( + &format!( + "{{^(https?)://{}/(.*)}}i", + Self::get_gitlab_domains_regex(&*self.config.borrow()) + ), + url, + Some(&mut m), + ) + .unwrap_or(false); + gl_matched + } { + let mut m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); + let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(); if m1 == "git" { m1 = "https".to_string(); } if !self.io.has_authentication(&m2) { let mut git_lab_util = GitLab::new( - self.io.as_ref(), - &self.config, - &self.process, - self.http_downloader - .as_ref() - .unwrap_or(&HttpDownloader::default()), - ); + self.io.clone_box(), + std::rc::Rc::clone(&self.config), + Some(std::rc::Rc::clone(&self.process)), + self.http_downloader.clone(), + )?; let message = "Cloning failed, enter your GitLab credentials to access private repos"; @@ -635,7 +667,7 @@ impl Git { if run_commands_inline( &auth_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 @@ -644,13 +676,13 @@ impl Git { } credentials = vec![rawurlencode(&username), rawurlencode(&password)]; - error_msg = self.process.get_error_output().to_string(); + error_msg = self.process.borrow().get_error_output().to_string(); } } else if let Some(m) = self.get_authentication_failure(url) { // private non-github/gitlab/bitbucket repo that failed to authenticate - let mut m1 = m.get(1).cloned().unwrap_or_default(); - let mut m2 = m.get(2).cloned().unwrap_or_default(); - let m3 = m.get(3).cloned().unwrap_or_default(); + let mut m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + let mut m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); + let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(); let mut auth_parts: Option = None; if str_contains(&m2, "@") { let parts = explode("@", &m2); @@ -674,16 +706,13 @@ impl Git { } } - self.io.write_error( - PhpMixed::String(format!( - " Authentication required ({}):", - m2 - )), + self.io.write_error3( + &format!(" Authentication required ({}):", m2), true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String(format!("{}", trim(&error_msg, None))), + self.io.write_error3( + &format!("{}", trim(&error_msg, None)), true, io_interface::VERBOSE, ); @@ -706,7 +735,7 @@ impl Git { self.io.ask_and_hide_answer(" Password: ".to_string()), ); auth = Some(auth_map); - store_auth = self.config.get("store-auths"); + store_auth = self.config.borrow_mut().get("store-auths"); } if let Some(auth_inner) = auth.as_ref() { @@ -731,27 +760,28 @@ impl Git { if run_commands_inline( &auth_url, - &mut self.process, + &mut *self.process.borrow_mut(), &mut last_command, command_output.as_deref_mut(), ) == 0 { self.io .set_authentication(m2.clone(), username, Some(password)); - let mut auth_helper = AuthHelper::new(self.io.as_ref(), &self.config); + let mut auth_helper = + AuthHelper::new(self.io.clone_box(), std::rc::Rc::clone(&self.config)); auth_helper.store_auth(&m2, &store_auth); return Ok(()); } credentials = vec![rawurlencode(&username), rawurlencode(&password)]; - error_msg = self.process.get_error_output().to_string(); + error_msg = self.process.borrow().get_error_output().to_string(); } } if initial_clone { if let Some(ref orig) = orig_cwd { - self.filesystem.remove_directory(orig); + self.filesystem.borrow_mut().remove_directory(orig); } } @@ -765,7 +795,7 @@ impl Git { } _ => last_command.as_string().unwrap_or("").to_string(), }; - let mut error_msg = self.process.get_error_output().to_string(); + let mut error_msg = self.process.borrow().get_error_output().to_string(); if (credentials.len() as i64) > 0 { last_command_str = self.mask_credentials(&last_command_str, &credentials); error_msg = self.mask_credentials(&error_msg, &credentials); @@ -787,11 +817,11 @@ impl Git { .unwrap_or(false) && composer_disable_network.as_deref() != Some("prime") { - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Aborting git mirror sync of {} as network is disabled", url - )), + ), true, io_interface::NORMAL, ); @@ -802,7 +832,7 @@ impl Git { // update the repo if it is a valid git repository let mut output = String::new(); if is_dir(dir) - && self.process.execute( + && self.process.borrow_mut().execute_args( &vec![ "git".to_string(), "rev-parse".to_string(), @@ -855,8 +885,8 @@ impl Git { ); if let Err(e) = try_result { - self.io.write_error( - PhpMixed::String(format!("Sync mirror failed: {}", e)), + self.io.write_error3( + &format!("Sync mirror failed: {}", e), true, io_interface::DEBUG, ); @@ -866,10 +896,10 @@ impl Git { return Ok(true); } - Self::check_for_repo_ownership_error(self.process.get_error_output(), dir, None)?; + Self::check_for_repo_ownership_error(self.process.borrow().get_error_output(), dir, None)?; // clean up directory and do a fresh clone into it - self.filesystem.remove_directory(dir); + self.filesystem.borrow_mut().remove_directory(dir); self.run_commands( vec![vec![ @@ -915,15 +945,12 @@ impl Git { if Preg::is_match(r"{^[a-f0-9]{40}$}", r#ref).unwrap_or(false) && pretty_version.is_some() { - let branch = Preg::replace( - r"{(?:^dev-|(?:\.x)?-dev$)}i", - "", - pretty_version.unwrap().to_string(), - ); + let branch = + Preg::replace(r"{(?:^dev-|(?:\.x)?-dev$)}i", "", &pretty_version.unwrap()); let mut branches: Option = None; let mut tags: Option = None; let mut output = String::new(); - if self.process.execute( + if self.process.borrow_mut().execute_args( &vec!["git".to_string(), "branch".to_string()], &mut output, Some(dir.to_string()), @@ -932,7 +959,7 @@ impl Git { branches = Some(output); } let mut output = String::new(); - if self.process.execute( + if self.process.borrow_mut().execute_args( &vec!["git".to_string(), "tag".to_string()], &mut output, Some(dir.to_string()), @@ -972,7 +999,9 @@ impl Git { Ok(false) } - pub fn get_no_show_signature_flag(process: &ProcessExecutor) -> String { + pub fn get_no_show_signature_flag( + process: &std::rc::Rc>, + ) -> String { let git_version = Self::get_version(process); if let Some(v) = git_version { if version_compare(&v, "2.10.0-rc0", ">=") { @@ -984,7 +1013,9 @@ impl Git { } /// @return list - pub fn get_no_show_signature_flags(process: &ProcessExecutor) -> Vec { + pub fn get_no_show_signature_flags( + process: &std::rc::Rc>, + ) -> Vec { let flags = Self::get_no_show_signature_flag(process); if flags.is_empty() { return vec![]; @@ -996,7 +1027,9 @@ impl Git { /// Checks if git version supports --no-commit-header flag (git 2.33+) /// /// @internal - pub fn supports_no_commit_header_flag(process: &ProcessExecutor) -> bool { + pub fn supports_no_commit_header_flag( + process: &std::rc::Rc>, + ) -> bool { let git_version = Self::get_version(process); git_version @@ -1010,7 +1043,7 @@ impl Git { /// @param list $arguments Additional arguments for git rev-list /// @return non-empty-list pub fn build_rev_list_command( - process: &ProcessExecutor, + process: &std::rc::Rc>, arguments: Vec, ) -> Vec { let mut command = vec!["git".to_string(), "rev-list".to_string()]; @@ -1028,20 +1061,23 @@ impl Git { /// "commit " before formatted output. This removes those lines. /// /// @internal - pub fn parse_rev_list_output(output: &str, process: &ProcessExecutor) -> String { + pub fn parse_rev_list_output( + output: &str, + process: &std::rc::Rc>, + ) -> String { // If git supports --no-commit-header, output is already clean if Self::supports_no_commit_header_flag(process) { return output.to_string(); } // Filter out "commit " lines for older git versions - Preg::replace(r"{^commit [a-f0-9]{40}\n?}m", "", output.to_string()) + Preg::replace(r"{^commit [a-f0-9]{40}\n?}m", "", output).unwrap_or_default() } fn check_ref_is_in_mirror(&mut self, dir: &str, r#ref: &str) -> Result { let mut output = String::new(); if is_dir(dir) - && self.process.execute( + && self.process.borrow_mut().execute_args( &vec![ "git".to_string(), "rev-parse".to_string(), @@ -1053,7 +1089,7 @@ impl Git { && trim(&output, None) == "." { let mut ignored_output = String::new(); - let exit_code = self.process.execute( + let exit_code = self.process.borrow_mut().execute_args( &vec![ "git".to_string(), "rev-parse".to_string(), @@ -1068,14 +1104,19 @@ impl Git { return Ok(true); } } - Self::check_for_repo_ownership_error(self.process.get_error_output(), dir, None)?; + Self::check_for_repo_ownership_error(self.process.borrow().get_error_output(), dir, None)?; Ok(false) } /// @return array|null - fn get_authentication_failure(&self, url: &str) -> Option> { - let m = Preg::is_match_strict_groups(r"{^(https?://)([^/]+)(.*)$}i", url)?; + fn get_authentication_failure(&self, url: &str) -> Option> { + let mut m: IndexMap = IndexMap::new(); + if !Preg::is_match_strict_groups3(r"{^(https?://)([^/]+)(.*)$}i", url, Some(&mut m)) + .unwrap_or(false) + { + return None; + } let auth_failures = [ "fatal: Authentication failed", @@ -1085,7 +1126,7 @@ impl Git { "fatal: could not read Username", ]; - let error_output = self.process.get_error_output(); + let error_output = self.process.borrow().get_error_output(); for auth_failure in &auth_failures { if strpos(error_output, auth_failure).is_some() { return Some(m); @@ -1112,7 +1153,7 @@ impl Git { let mut output_mixed = PhpMixed::String(String::new()); if is_local_path_repository { let mut output = String::new(); - self.process.execute( + self.process.borrow_mut().execute_args( &vec![ "git".to_string(), "remote".to_string(), @@ -1154,12 +1195,23 @@ impl Git { let lines = self .process + .borrow() .split_lines(output_mixed.as_string().unwrap_or("")); for line in lines { - if let Some(matches) = - Preg::is_match_strict_groups(r"{^\s*HEAD branch:\s(.+)\s*$}m", &line) + let mut matches: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*HEAD branch:\s(.+)\s*$}m", + &line, + Some(&mut matches), + ) + .unwrap_or(false) { - return Ok(Some(matches.get(1).cloned().unwrap_or_default())); + return Ok(Some( + matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(), + )); } } @@ -1168,11 +1220,11 @@ impl Git { match result { Ok(v) => v, Err(e) => { - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Failed to fetch root identifier from remote: {}", e - )), + ), true, io_interface::DEBUG, ); @@ -1181,7 +1233,7 @@ impl Git { } } - pub fn clean_env(process: &ProcessExecutor) { + pub fn clean_env(process: &std::rc::Rc>) { // PHP: $process ?? new ProcessExecutor() let git_version = Self::get_version(process); if let Some(v) = git_version { @@ -1249,7 +1301,7 @@ impl Git { clearstatcache(); let mut ignored_output = String::new(); - if self.process.execute( + if self.process.borrow_mut().execute_args( &vec!["git".to_string(), "--version".to_string()], &mut ignored_output, None, @@ -1259,7 +1311,7 @@ impl Git { message: Url::sanitize(format!( "Failed to clone {}, git was not found, check that it is installed and in your PATH env.\n\n{}", url, - self.process.get_error_output() + self.process.borrow().get_error_output() )), code: 0, } @@ -1276,7 +1328,9 @@ impl Git { /// Retrieves the current git version. /// /// @return string|null The git version number, if present. - pub fn get_version(process: &ProcessExecutor) -> Option { + pub fn get_version( + process: &std::rc::Rc>, + ) -> Option { let mut version = VERSION.lock().unwrap(); if version.is_none() { *version = Some(None); @@ -1285,10 +1339,15 @@ impl Git { // For now, mimic the call signature (compilation fix is Phase B) let exit_code: i64 = 0; // process.execute(&["git", "--version"].map(String::from).to_vec(), &mut output, None); if exit_code == 0 { - if let Some(matches) = - Preg::is_match_strict_groups(r"/^git version (\d+(?:\.\d+)+)/m", &output) + let mut matches: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"/^git version (\d+(?:\.\d+)+)/m", + &output, + Some(&mut matches), + ) + .unwrap_or(false) { - *version = Some(matches.get(1).cloned()); + *version = Some(matches.get(&CaptureKey::ByIndex(1)).cloned()); } } } diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs index 5fa88a6..cfb3d0c 100644 --- a/crates/shirabe/src/util/github.rs +++ b/crates/shirabe/src/util/github.rs @@ -2,7 +2,7 @@ use crate::io::io_interface; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{PhpMixed, date, stripos, strtolower}; use crate::config::Config; @@ -15,9 +15,9 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct GitHub { io: Box, - config: Config, - process: ProcessExecutor, - http_downloader: HttpDownloader, + config: std::rc::Rc>, + process: std::rc::Rc>, + http_downloader: std::rc::Rc>, } impl GitHub { @@ -26,14 +26,20 @@ impl GitHub { pub fn new( io: Box, - config: Config, - process: Option, - http_downloader: Option, + config: std::rc::Rc>, + process: Option>>, + http_downloader: Option>>, ) -> anyhow::Result { - let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); + let process = process.unwrap_or_else(|| { + std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(&*io))) + }); let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?, + None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader( + &*io, + &config, + IndexMap::new(), + )?)), }; Ok(Self { io, @@ -44,7 +50,7 @@ impl GitHub { } pub fn authorize_oauth(&mut self, origin_url: &str) -> bool { - let github_domains = self.config.get("github-domains"); + let github_domains = self.config.borrow_mut().get("github-domains"); let domains = match github_domains.as_array() { Some(arr) => arr.clone(), None => return false, @@ -55,7 +61,7 @@ impl GitHub { } let mut output = String::new(); - if self.process.execute( + if self.process.borrow_mut().execute_args( &[ "git".to_string(), "config".to_string(), @@ -82,16 +88,13 @@ impl GitHub { message: Option<&str>, ) -> anyhow::Result { if let Some(msg) = message { - self.io.write_error( - PhpMixed::String(msg.to_string()), - true, - io_interface::NORMAL, - ); + self.io.write_error3(msg, true, io_interface::NORMAL); } let mut note = "Composer".to_string(); let expose_hostname = self .config + .borrow_mut() .get("github-expose-hostname") .as_bool() .unwrap_or(false); @@ -99,7 +102,8 @@ impl GitHub { let mut output = String::new(); if self .process - .execute(&["hostname".to_string()], &mut output, None) + .borrow_mut() + .execute_args(&["hostname".to_string()], &mut output, None) == 0 { note += &format!(" on {}", output.trim()); @@ -107,105 +111,87 @@ impl GitHub { } note += &format!(" {}", date("Y-m-d Hi", None)); - let local_auth_config = self.config.get_local_auth_config_source(); - - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String( - "You need to provide a GitHub access token.".to_string(), - )), - Box::new(PhpMixed::String(format!( - "Tokens will be stored in plain text in \"{}\" for future use by Composer.", - local_auth_config - .as_ref() - .map(|c| format!("{} OR ", c.get_name())) - .unwrap_or_default() - + &self.config.get_auth_config_source().get_name() - ))), - Box::new(PhpMixed::String( - "Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.".to_string(), - )), - Box::new(PhpMixed::String(String::new())), - Box::new(PhpMixed::String( - "Carefully consider the following options in order:".to_string(), - )), - Box::new(PhpMixed::String(String::new())), - ]), - true, - io_interface::NORMAL, - ); + let local_auth_config = self.config.borrow().get_local_auth_config_source(); + + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String( + "You need to provide a GitHub access token.".to_string(), + )), + Box::new(PhpMixed::String(format!( + "Tokens will be stored in plain text in \"{}\" for future use by Composer.", + local_auth_config + .as_ref() + .map(|c| format!("{} OR ", c.get_name())) + .unwrap_or_default() + + &self.config.borrow().get_auth_config_source().get_name() + ))), + Box::new(PhpMixed::String( + "Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.".to_string(), + )), + Box::new(PhpMixed::String(String::new())), + Box::new(PhpMixed::String( + "Carefully consider the following options in order:".to_string(), + )), + Box::new(PhpMixed::String(String::new())), + ]), true, io_interface::NORMAL); let encoded_note = shirabe_php_shim::rawurlencode(¬e).replace("%20", "+"); - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String( - "1. When you don't use 'vcs' type 'repositories' in composer.json and do not need to clone source or download dist files".to_string(), - )), - Box::new(PhpMixed::String( - "from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.".to_string(), - )), - Box::new(PhpMixed::String( - "Use the following URL to create such a token:".to_string(), - )), - Box::new(PhpMixed::String(format!( - "https://{}/settings/personal-access-tokens/new?name={}", - origin_url, encoded_note - ))), - Box::new(PhpMixed::String(String::new())), - ]), - true, - io_interface::NORMAL, - ); - - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String( - "2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with".to_string(), - )), - Box::new(PhpMixed::String( - "repository \"content\" read-only permissions. You can start with the following URL, but you may need to change the resource owner".to_string(), - )), - Box::new(PhpMixed::String( - "to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.".to_string(), - )), - Box::new(PhpMixed::String(format!( - "https://{}/settings/personal-access-tokens/new?contents=read&name={}", - origin_url, encoded_note - ))), - Box::new(PhpMixed::String(String::new())), - ]), - true, - io_interface::NORMAL, - ); - - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String( - "3. A \"classic\" token grants broad permissions on your behalf to all repositories accessible by you.".to_string(), - )), - Box::new(PhpMixed::String( - "This may include write permissions, even though not needed by Composer. Use it only when you need to access".to_string(), - )), - Box::new(PhpMixed::String( - "private repositories across multiple organisations at the same time and using directory-specific authentication sources".to_string(), - )), - Box::new(PhpMixed::String( - "is not an option. You can generate a classic token here:".to_string(), - )), - Box::new(PhpMixed::String(format!( - "https://{}/settings/tokens/new?scopes=repo&description={}", - origin_url, encoded_note - ))), - Box::new(PhpMixed::String(String::new())), - ]), - true, - io_interface::NORMAL, - ); - - self.io.write_error( - PhpMixed::String( - "For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth".to_string(), - ), + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String( + "1. When you don't use 'vcs' type 'repositories' in composer.json and do not need to clone source or download dist files".to_string(), + )), + Box::new(PhpMixed::String( + "from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.".to_string(), + )), + Box::new(PhpMixed::String( + "Use the following URL to create such a token:".to_string(), + )), + Box::new(PhpMixed::String(format!( + "https://{}/settings/personal-access-tokens/new?name={}", + origin_url, encoded_note + ))), + Box::new(PhpMixed::String(String::new())), + ]), true, io_interface::NORMAL); + + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String( + "2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with".to_string(), + )), + Box::new(PhpMixed::String( + "repository \"content\" read-only permissions. You can start with the following URL, but you may need to change the resource owner".to_string(), + )), + Box::new(PhpMixed::String( + "to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.".to_string(), + )), + Box::new(PhpMixed::String(format!( + "https://{}/settings/personal-access-tokens/new?contents=read&name={}", + origin_url, encoded_note + ))), + Box::new(PhpMixed::String(String::new())), + ]), true, io_interface::NORMAL); + + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String( + "3. A \"classic\" token grants broad permissions on your behalf to all repositories accessible by you.".to_string(), + )), + Box::new(PhpMixed::String( + "This may include write permissions, even though not needed by Composer. Use it only when you need to access".to_string(), + )), + Box::new(PhpMixed::String( + "private repositories across multiple organisations at the same time and using directory-specific authentication sources".to_string(), + )), + Box::new(PhpMixed::String( + "is not an option. You can generate a classic token here:".to_string(), + )), + Box::new(PhpMixed::String(format!( + "https://{}/settings/tokens/new?scopes=repo&description={}", + origin_url, encoded_note + ))), + Box::new(PhpMixed::String(String::new())), + ]), true, io_interface::NORMAL); + + self.io.write_error3( + "For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth", true, io_interface::NORMAL, ); @@ -227,15 +213,13 @@ impl GitHub { .to_string(); if token.is_empty() { - self.io.write_error( - PhpMixed::String("No token given, aborting.".to_string()), + self.io.write_error3( + "No token given, aborting.", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com \"".to_string(), - ), + self.io.write_error3( + "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com \"", true, io_interface::NORMAL, ); @@ -263,20 +247,19 @@ impl GitHub { match self .http_downloader + .borrow_mut() .get(&format!("https://{}", api_url), &http_options) { Ok(_) => {} Err(te) => { if te.code == 403 || te.code == 401 { - self.io.write_error( - PhpMixed::String("Invalid token provided.".to_string()), + self.io.write_error3( + "Invalid token provided.", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String( - "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com \"".to_string(), - ), + self.io.write_error3( + "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com \"", true, io_interface::NORMAL, ); @@ -286,12 +269,18 @@ impl GitHub { } } - let use_local = - store_in_local_auth_config && self.config.get_local_auth_config_source().is_some(); + let use_local = store_in_local_auth_config + && self + .config + .borrow() + .get_local_auth_config_source() + .is_some(); let auth_config_source_name; if use_local { - let mut auth_config_source = self.config.get_local_auth_config_source().unwrap(); + let mut auth_config_source = + self.config.borrow().get_local_auth_config_source().unwrap(); self.config + .borrow() .get_config_source() .remove_config_setting(&format!("github-oauth.{}", origin_url))?; auth_config_source.add_config_setting( @@ -299,8 +288,9 @@ impl GitHub { PhpMixed::String(token), )?; } else { - let mut auth_config_source = self.config.get_auth_config_source(); + let mut auth_config_source = self.config.borrow().get_auth_config_source(); self.config + .borrow() .get_config_source() .remove_config_setting(&format!("github-oauth.{}", origin_url))?; auth_config_source.add_config_setting( @@ -309,8 +299,8 @@ impl GitHub { )?; } - self.io.write_error( - PhpMixed::String("Token stored successfully.".to_string()), + self.io.write_error3( + "Token stored successfully.", true, io_interface::NORMAL, ); @@ -358,8 +348,11 @@ impl GitHub { if stripos(header, "x-github-sso: required").is_none() { continue; } - if let Some(caps) = Preg::match_strict_groups(r"{\burl=(?P[^\s;]+)}", header) { - return caps.get("url").cloned(); + let mut caps: IndexMap = IndexMap::new(); + if Preg::match_strict_groups3(r"{\burl=(?P[^\s;]+)}", header, Some(&mut caps)) + .unwrap_or(false) + { + return caps.get(&CaptureKey::ByName("url".to_string())).cloned(); } } diff --git a/crates/shirabe/src/util/gitlab.rs b/crates/shirabe/src/util/gitlab.rs index 191e69d..11b7a21 100644 --- a/crates/shirabe/src/util/gitlab.rs +++ b/crates/shirabe/src/util/gitlab.rs @@ -15,22 +15,28 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct GitLab { pub(crate) io: Box, - pub(crate) config: Config, - pub(crate) process: ProcessExecutor, - pub(crate) http_downloader: HttpDownloader, + pub(crate) config: std::rc::Rc>, + pub(crate) process: std::rc::Rc>, + pub(crate) http_downloader: std::rc::Rc>, } impl GitLab { pub fn new( io: Box, - config: Config, - process: Option, - http_downloader: Option, + config: std::rc::Rc>, + process: Option>>, + http_downloader: Option>>, ) -> anyhow::Result { - let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); + let process = process.unwrap_or_else(|| { + std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(&*io))) + }); let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?, + None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader( + &*io, + &config, + IndexMap::new(), + )?)), }; Ok(Self { io, @@ -45,7 +51,7 @@ impl GitLab { let bc_origin_url = Preg::replace("{:\\d+}", "", origin_url).unwrap_or_else(|_| origin_url.to_string()); - let gitlab_domains = self.config.get("gitlab-domains"); + let gitlab_domains = self.config.borrow_mut().get("gitlab-domains"); let domains = match gitlab_domains.as_array() { Some(arr) => arr.clone(), None => return false, @@ -60,7 +66,7 @@ impl GitLab { // if available use token from git config let mut output = String::new(); - if self.process.execute( + if self.process.borrow_mut().execute_args( &[ "git".to_string(), "config".to_string(), @@ -81,7 +87,7 @@ impl GitLab { // if available use deploy token from git config let mut token_user = String::new(); let mut token_password = String::new(); - if self.process.execute( + if self.process.borrow_mut().execute_args( &[ "git".to_string(), "config".to_string(), @@ -90,7 +96,7 @@ impl GitLab { &mut token_user, None, ) == 0 - && self.process.execute( + && self.process.borrow_mut().execute_args( &[ "git".to_string(), "config".to_string(), @@ -109,7 +115,7 @@ impl GitLab { } // if available use token from composer config - let auth_tokens = self.config.get("gitlab-token"); + let auth_tokens = self.config.borrow_mut().get("gitlab-token"); let mut token: Option = None; @@ -168,68 +174,53 @@ impl GitLab { message: Option<&str>, ) -> anyhow::Result { if let Some(msg) = message { - self.io.write_error( - PhpMixed::String(msg.to_string()), - true, - io_interface::NORMAL, - ); + self.io.write_error3(msg, true, io_interface::NORMAL); } - let local_auth_config = self.config.get_local_auth_config_source(); + let local_auth_config = self.config.borrow().get_local_auth_config_source(); let personal_access_token_link = format!( "{}://{}/-/user_settings/personal_access_tokens", scheme, origin_url ); let revoke_link = format!("{}://{}/-/user_settings/applications", scheme, origin_url); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "A token will be created and stored in \"{}\", your password will never be stored", local_auth_config .as_ref() .map(|c| format!("{} OR ", c.get_name())) .unwrap_or_default() - + &self.config.get_auth_config_source().get_name() - )), - true, - io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::String("To revoke access to this token you can visit:".to_string()), - true, - io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::String(revoke_link.clone()), - true, - io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::String( - "Alternatively you can setup an personal access token on:".to_string(), + + &self.config.borrow().get_auth_config_source().get_name() ), true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String(personal_access_token_link.clone()), + self.io.write_error3( + "To revoke access to this token you can visit:", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String("and store it under \"gitlab-token\" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.".to_string()), + self.io + .write_error3(&revoke_link, true, io_interface::NORMAL); + self.io.write_error3( + "Alternatively you can setup an personal access token on:", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String("https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token".to_string()), + self.io + .write_error3(&personal_access_token_link, true, io_interface::NORMAL); + self.io.write_error3( + "and store it under \"gitlab-token\" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String("for more details.".to_string()), + self.io.write_error3( + "https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token", true, io_interface::NORMAL, ); + self.io + .write_error3("for more details.", true, io_interface::NORMAL); let mut store_in_local_auth_config = false; if local_auth_config.is_some() { @@ -261,41 +252,41 @@ impl GitLab { .and_then(|v| v.as_string()) == Some("invalid_grant"); if is_invalid_grant { - self.io.write_error( - PhpMixed::String("Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token".to_string()), + self.io.write_error3( + "Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token", true, io_interface::NORMAL, ); } else { - self.io.write_error( - PhpMixed::String("Bad credentials.".to_string()), + self.io.write_error3( + "Bad credentials.", true, io_interface::NORMAL, ); } } else { - self.io.write_error( - PhpMixed::String("Maximum number of login attempts exceeded. Please try again later.".to_string()), + self.io.write_error3( + "Maximum number of login attempts exceeded. Please try again later.", true, io_interface::NORMAL, ); } - self.io.write_error( - PhpMixed::String("You can also manually create a personal access token enabling the \"read_api\" scope at:".to_string()), + self.io.write_error3( + "You can also manually create a personal access token enabling the \"read_api\" scope at:", true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String(personal_access_token_link.clone()), + self.io.write_error3( + &personal_access_token_link, true, io_interface::NORMAL, ); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Add it using \"composer config --global --auth gitlab-token.{} \"", origin_url - )), + ), true, io_interface::NORMAL, ); @@ -342,7 +333,7 @@ impl GitLab { )?; } } else { - let mut auth_config_source = self.config.get_auth_config_source(); + let mut auth_config_source = self.config.borrow().get_auth_config_source(); if has_expires_in { auth_config_source.add_config_setting( &format!("gitlab-oauth.{}", origin_url), @@ -375,8 +366,8 @@ impl GitLab { Ok(r) => r, Err(e) => match e.downcast::() { Ok(te) => { - self.io.write_error( - PhpMixed::String(format!("Couldn't refresh access token: {}", te.message)), + self.io.write_error3( + &format!("Couldn't refresh access token: {}", te.message), true, io_interface::NORMAL, ); @@ -400,10 +391,13 @@ impl GitLab { ); // store value in user config in auth file - self.config.get_auth_config_source().add_config_setting( - &format!("gitlab-oauth.{}", origin_url), - Self::build_oauth_config(&response, &access_token), - )?; + self.config + .borrow() + .get_auth_config_source() + .add_config_setting( + &format!("gitlab-oauth.{}", origin_url), + Self::build_oauth_config(&response, &access_token), + )?; Ok(true) } @@ -454,23 +448,21 @@ impl GitLab { let token = self .http_downloader + .borrow_mut() .get( &format!("{}://{}/oauth/token", scheme, api_url), &PhpMixed::Array(options), )? .decode_json()?; - self.io.write_error( - PhpMixed::String("Token successfully created".to_string()), - true, - io_interface::NORMAL, - ); + self.io + .write_error3("Token successfully created", true, io_interface::NORMAL); Ok(token) } pub fn is_oauth_expired(&self, origin_url: &str) -> bool { - let auth_tokens = self.config.get("gitlab-oauth"); + let auth_tokens = self.config.borrow_mut().get("gitlab-oauth"); if let Some(map) = auth_tokens.as_array() { if let Some(token_info) = map.get(origin_url) { if let Some(token_map) = token_info.as_array() { @@ -489,7 +481,7 @@ impl GitLab { } fn refresh_token(&mut self, scheme: &str, origin_url: &str) -> anyhow::Result { - let auth_tokens = self.config.get("gitlab-oauth"); + let auth_tokens = self.config.borrow_mut().get("gitlab-oauth"); let refresh_token = auth_tokens .as_array() .and_then(|map| map.get(origin_url)) @@ -543,22 +535,23 @@ impl GitLab { let token = self .http_downloader + .borrow_mut() .get( &format!("{}://{}/oauth/token", scheme, origin_url), &PhpMixed::Array(options), )? .decode_json()?; - self.io.write_error( - PhpMixed::String("GitLab token successfully refreshed".to_string()), + self.io.write_error3( + "GitLab token successfully refreshed", true, io_interface::VERY_VERBOSE, ); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "To revoke access to this token you can visit {}://{}/-/user_settings/applications", scheme, origin_url - )), + ), true, io_interface::VERY_VERBOSE, ); diff --git a/crates/shirabe/src/util/hg.rs b/crates/shirabe/src/util/hg.rs index c3f4b6e..fa25a78 100644 --- a/crates/shirabe/src/util/hg.rs +++ b/crates/shirabe/src/util/hg.rs @@ -14,12 +14,16 @@ static VERSION: OnceLock> = OnceLock::new(); #[derive(Debug)] pub struct Hg { io: Box, - config: Config, - process: ProcessExecutor, + config: std::rc::Rc>, + process: std::rc::Rc>, } impl Hg { - pub fn new(io: &dyn IOInterface, config: &Config, process: &ProcessExecutor) -> Self { + pub fn new( + io: &dyn IOInterface, + config: &Config, + process: &std::rc::Rc>, + ) -> Self { todo!() } @@ -29,14 +33,19 @@ impl Hg { url: String, cwd: Option, ) -> Result<()> { - self.config.prohibit_url_by_config(&url, &*self.io)?; + self.config.borrow_mut().prohibit_url_by_config( + &url, + Some(&*self.io), + &indexmap::IndexMap::new(), + )?; // Try as is let command = command_callable(url.clone()); let mut ignored_output = String::new(); if self .process - .execute(&command, &mut ignored_output, cwd.clone()) + .borrow_mut() + .execute_args(&command, &mut ignored_output, cwd.clone()) == 0 { return Ok(()); @@ -82,11 +91,16 @@ impl Hg { let command = command_callable(authenticated_url); let mut ignored_output = String::new(); - if self.process.execute(&command, &mut ignored_output, cwd) == 0 { + if self + .process + .borrow_mut() + .execute_args(&command, &mut ignored_output, cwd) + == 0 + { return Ok(()); } - let error = self.process.get_error_output(); + let error = self.process.borrow().get_error_output(); return self .throw_exception(&format!("Failed to clone {}, \n\n{}", url, error), &url); } @@ -106,7 +120,7 @@ impl Hg { Url::sanitize(&format!( "Failed to clone {}, hg was not found, check that it is installed and in your PATH env.\n\n{}", url, - self.process.get_error_output() + self.process.borrow().get_error_output() )) ); } @@ -114,11 +128,13 @@ impl Hg { anyhow::bail!("{}", Url::sanitize(message)); } - pub fn get_version(process: &ProcessExecutor) -> Option<&'static str> { + pub fn get_version( + process: &std::rc::Rc>, + ) -> Option<&'static str> { VERSION .get_or_init(|| { let mut output = String::new(); - if process.execute( + if process.borrow_mut().execute_args( &["hg".to_string(), "--version".to_string()], &mut output, None, diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs index d335b8b..c475ad3 100644 --- a/crates/shirabe/src/util/http/curl_downloader.rs +++ b/crates/shirabe/src/util/http/curl_downloader.rs @@ -51,7 +51,7 @@ pub struct CurlDownloader { /// @var IOInterface io: Box, /// @var Config - config: Config, + config: std::rc::Rc>, /// @var AuthHelper auth_helper: AuthHelper, /// @var float @@ -124,7 +124,7 @@ impl CurlDownloader { /// @param mixed[] $options pub fn new( io: Box, - config: Config, + config: std::rc::Rc>, _options: IndexMap, _disable_tls: bool, ) -> Self { @@ -336,10 +336,11 @@ impl CurlDownloader { let original_options = options.clone(); // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 - if !Preg::is_match(r"{^http://(repo\.)?packagist\.org/p/}", url) + if !Preg::is_match(r"{^http://(repo\.)?packagist\.org/p/}", url).unwrap_or(false) || (strpos(url, "$").is_none() && strpos(url, "%24").is_none()) { self.config + .borrow_mut() .prohibit_url_by_config(url, Some(&*self.io), &options)?; } @@ -681,13 +682,13 @@ impl CurlDownloader { if attributes.get("redirects").and_then(|v| v.as_int()) == Some(0) && attributes.get("retries").and_then(|v| v.as_int()) == Some(0) { - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Downloading {}{}{}", Url::sanitize(url.to_string()), using_proxy, if_modified - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -830,11 +831,8 @@ impl CurlDownloader { && !TIMEOUT_WARNING.load(Ordering::Relaxed) { TIMEOUT_WARNING.store(true, Ordering::Relaxed); - self.io.write_error( - PhpMixed::String( - "A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode." - .to_string(), - ), + self.io.write_error3( + "A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.", true, crate::io::io_interface::NORMAL, ); @@ -904,8 +902,8 @@ impl CurlDownloader { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected attributes.insert("ipResolve".to_string(), PhpMixed::Int(4)); } - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Retrying ({}) {} due to curl error {}", job.get("attributes") .and_then(|v| v.as_array()) @@ -920,7 +918,7 @@ impl CurlDownloader { .to_string() ), errno - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -936,8 +934,8 @@ impl CurlDownloader { if errno == 55 /* CURLE_SEND_ERROR */ { - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Retrying ({}) {} due to curl error {}", job.get("attributes") .and_then(|v| v.as_array()) @@ -952,7 +950,7 @@ impl CurlDownloader { .to_string() ), errno - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -1073,8 +1071,8 @@ impl CurlDownloader { .map(|(k, v)| (k.clone(), (**v).clone())) .collect(), )); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "[{}] {}", status_code.unwrap_or(0), Url::sanitize( @@ -1083,7 +1081,7 @@ impl CurlDownloader { .unwrap_or("") .to_string() ) - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -1149,8 +1147,8 @@ impl CurlDownloader { .map(|(k, v)| (k.clone(), (**v).clone())) .collect(), )); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "[{}] {}", status_code.unwrap_or(0), Url::sanitize( @@ -1159,7 +1157,7 @@ impl CurlDownloader { .unwrap_or("") .to_string() ) - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -1292,8 +1290,8 @@ impl CurlDownloader { .unwrap_or(0) < self.max_retries { - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Retrying ({}) {} due to status code {}", job.get("attributes") .and_then(|v| v.as_array()) @@ -1308,7 +1306,7 @@ impl CurlDownloader { .to_string() ), sc - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -1598,8 +1596,8 @@ impl CurlDownloader { } if !target_url.is_empty() { - self.io.write_error( - PhpMixed::String(sprintf( + self.io.write_error3( + &sprintf( "Following redirect (%u) %s", &[ PhpMixed::Int( @@ -1612,7 +1610,7 @@ impl CurlDownloader { ), PhpMixed::String(Url::sanitize(target_url.clone())), ], - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -1697,7 +1695,7 @@ impl CurlDownloader { } // check for gitlab 404 when downloading archives - let gitlab_domains = self.config.get("gitlab-domains"); + let gitlab_domains = self.config.borrow_mut().get("gitlab-domains"); let gitlab_domains_list: Vec> = match gitlab_domains { PhpMixed::List(l) => l, _ => Vec::new(), @@ -1788,7 +1786,7 @@ impl CurlDownloader { PhpMixed::Array(a) => a.into_iter().map(|(k, v)| (k, *v)).collect(), _ => IndexMap::new(), }; - let origin = Url::get_origin(&self.config, url); + let origin = Url::get_origin(&*self.config.borrow(), url); let copy_to = job .get("filename") diff --git a/crates/shirabe/src/util/http/proxy_manager.rs b/crates/shirabe/src/util/http/proxy_manager.rs index 5ed8269..2576dcb 100644 --- a/crates/shirabe/src/util/http/proxy_manager.rs +++ b/crates/shirabe/src/util/http/proxy_manager.rs @@ -50,10 +50,10 @@ impl ProxyManager { request_url: &str, ) -> Result { if let Some(ref error) = self.error { - return Err(TransportException::new(format!( - "Unable to use a proxy: {}", - error - ))); + return Err(TransportException::new( + format!("Unable to use a proxy: {}", error), + 0, + )); } let scheme = request_url.split("://").next().unwrap_or("").to_string(); @@ -85,19 +85,19 @@ impl ProxyManager { // PHP_SAPI is always 'cli' for this application let (env, name) = Self::get_proxy_env("http_proxy"); if let Some(env) = env { - self.http_proxy = Some(ProxyItem::new(env, name)); + self.http_proxy = Some(ProxyItem::new(env, name)?); } if self.http_proxy.is_none() { let (env, name) = Self::get_proxy_env("cgi_http_proxy"); if let Some(env) = env { - self.http_proxy = Some(ProxyItem::new(env, name)); + self.http_proxy = Some(ProxyItem::new(env, name)?); } } let (env, name) = Self::get_proxy_env("https_proxy"); if let Some(env) = env { - self.https_proxy = Some(ProxyItem::new(env, name)); + self.https_proxy = Some(ProxyItem::new(env, name)?); } let (env, _name) = Self::get_proxy_env("no_proxy"); @@ -122,7 +122,7 @@ impl ProxyManager { fn no_proxy(&self, request_url: &str) -> bool { match &self.no_proxy_handler { None => false, - Some(handler) => handler.test(request_url), + Some(handler) => handler.test(request_url).unwrap_or(false), } } } diff --git a/crates/shirabe/src/util/http/response.rs b/crates/shirabe/src/util/http/response.rs index cf238c7..62458d6 100644 --- a/crates/shirabe/src/util/http/response.rs +++ b/crates/shirabe/src/util/http/response.rs @@ -41,7 +41,7 @@ impl Response { pub fn get_status_message(&self) -> Option { let mut value = None; for header in &self.headers { - if Preg::is_match(r"(?i)^HTTP/\S+ \d+", header) { + if Preg::is_match(r"(?i)^HTTP/\S+ \d+", header).unwrap_or(false) { // In case of redirects, headers contain the headers of all responses // so we can not return directly and need to keep iterating value = Some(header.clone()); diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs index 97b1f6e..71385ce 100644 --- a/crates/shirabe/src/util/http_downloader.rs +++ b/crates/shirabe/src/util/http_downloader.rs @@ -4,7 +4,7 @@ use anyhow::Result; use indexmap::IndexMap; use crate::util::silencer::Silencer; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_external_packages::react::promise::promise::Promise; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; use shirabe_php_shim::{ @@ -34,7 +34,7 @@ pub struct HttpDownloader { /// @var IOInterface io: Box, /// @var Config - config: Config, + config: std::rc::Rc>, /// @var array jobs: IndexMap, /// @var mixed[] @@ -88,13 +88,12 @@ impl HttpDownloader { /// @param mixed[] $options The options pub fn new( io: Box, - config: Config, + config: std::rc::Rc>, options: IndexMap, disable_tls: bool, ) -> Self { let disabled = Platform::get_env("COMPOSER_DISABLE_NETWORK") - .as_bool() - .unwrap_or(false); + .map_or(false, |s| !s.is_empty() && s != "0"); // Setup TLS options // The cafile option can be set via config.json @@ -125,8 +124,8 @@ impl HttpDownloader { let curl = if Self::is_curl_enabled() { Some(CurlDownloader::new( - &*io, - &config, + io.clone_box(), + std::rc::Rc::clone(&config), options.clone(), disable_tls, )) @@ -135,8 +134,8 @@ impl HttpDownloader { }; let rfs = Some(RemoteFilesystem::new( - &*io, - &config, + io.clone_box(), + std::rc::Rc::clone(&config), options.clone(), disable_tls, )); @@ -328,7 +327,7 @@ impl HttpDownloader { let id = self.id_gen; self.id_gen += 1; - let origin = Url::get_origin(&self.config, &request.url); + let origin = Url::get_origin(&*self.config.borrow(), &request.url); let job = Job { id, @@ -354,13 +353,28 @@ impl HttpDownloader { } // capture username/password from URL if there is one - if let Some(m) = - Preg::is_match_strict_groups(r"{^https?://([^:/]+):([^@/]+)@([^/]+)}i", &request.url) + let mut m: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^https?://([^:/]+):([^@/]+)@([^/]+)}i", + &request.url, + Some(&mut m), + ) + .unwrap_or(false) { self.io.set_authentication( origin.clone(), - rawurldecode(m.get(1).cloned().unwrap_or_default().as_str()), - Some(rawurldecode(m.get(2).cloned().unwrap_or_default().as_str())), + rawurldecode( + m.get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default() + .as_str(), + ), + Some(rawurldecode( + m.get(&CaptureKey::ByIndex(2)) + .cloned() + .unwrap_or_default() + .as_str(), + )), ); } @@ -723,7 +737,7 @@ impl HttpDownloader { return false; } - if !Preg::is_match(r"{^https?://}i", &job.request.url) { + if !Preg::is_match(r"{^https?://}i", &job.request.url).unwrap_or(false) { return false; } diff --git a/crates/shirabe/src/util/loop.rs b/crates/shirabe/src/util/loop.rs index 189b8a3..9ffee8f 100644 --- a/crates/shirabe/src/util/loop.rs +++ b/crates/shirabe/src/util/loop.rs @@ -10,21 +10,21 @@ use shirabe_php_shim::microtime; #[derive(Debug)] pub struct Loop { - http_downloader: HttpDownloader, - process_executor: Option, + http_downloader: std::rc::Rc>, + process_executor: Option>>, current_promises: IndexMap>>, wait_index: i64, } impl Loop { pub fn new( - mut http_downloader: HttpDownloader, - process_executor: Option, + http_downloader: std::rc::Rc>, + process_executor: Option>>, ) -> Self { - http_downloader.enable_async(); + http_downloader.borrow_mut().enable_async(); - let process_executor = process_executor.map(|mut pe| { - pe.enable_async(); + let process_executor = process_executor.map(|pe| { + pe.borrow_mut().enable_async(); pe }); @@ -36,11 +36,13 @@ impl Loop { } } - pub fn get_http_downloader(&self) -> &HttpDownloader { + pub fn get_http_downloader(&self) -> &std::rc::Rc> { &self.http_downloader } - pub fn get_process_executor(&self) -> Option<&ProcessExecutor> { + pub fn get_process_executor( + &self, + ) -> Option<&std::rc::Rc>> { self.process_executor.as_ref() } @@ -66,9 +68,9 @@ impl Loop { if let Some(ref progress) = progress { let mut total_jobs: i64 = 0; - total_jobs += self.http_downloader.count_active_jobs(); + total_jobs += self.http_downloader.borrow_mut().count_active_jobs(None); if let Some(ref pe) = self.process_executor { - total_jobs += pe.count_active_jobs(); + total_jobs += pe.borrow_mut().count_active_jobs(None); } progress.start(total_jobs); } @@ -77,9 +79,9 @@ impl Loop { loop { let mut active_jobs: i64 = 0; - active_jobs += self.http_downloader.count_active_jobs(); + active_jobs += self.http_downloader.borrow_mut().count_active_jobs(None); if let Some(ref pe) = self.process_executor { - active_jobs += pe.count_active_jobs(); + active_jobs += pe.borrow_mut().count_active_jobs(None); } if let Some(ref progress) = progress { diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs index a647dbb..d4d39ea 100644 --- a/crates/shirabe/src/util/perforce.rs +++ b/crates/shirabe/src/util/perforce.rs @@ -29,12 +29,12 @@ pub struct Perforce { pub(crate) p4_client_spec: String, pub(crate) p4_depot_type: Option, pub(crate) p4_branch: Option, - pub(crate) process: ProcessExecutor, + pub(crate) process: std::rc::Rc>, pub(crate) unique_perforce_client_name: String, pub(crate) windows_flag: bool, pub(crate) command_result: String, pub(crate) io: Box, - pub(crate) filesystem: Option, + pub(crate) filesystem: Option>>, } impl Perforce { @@ -43,7 +43,7 @@ impl Perforce { repo_config: IndexMap, port: String, path: String, - process: ProcessExecutor, + process: std::rc::Rc>, is_windows: bool, io: Box, ) -> Self { @@ -75,7 +75,7 @@ impl Perforce { repo_config: IndexMap, port: String, path: String, - process: ProcessExecutor, + process: std::rc::Rc>, io: Box, ) -> Self { Self::new(repo_config, port, path, process, Platform::is_windows(), io) @@ -83,7 +83,7 @@ impl Perforce { pub fn check_server_exists(url: &str, process_executor: &mut ProcessExecutor) -> bool { let mut ignored_output = String::new(); - process_executor.execute( + process_executor.execute_args( &vec![ "p4".to_string(), "-p".to_string(), @@ -152,7 +152,7 @@ impl Perforce { )); let client_spec = self.get_p4_client_spec(); let file_system = self.get_filesystem(); - file_system.remove(&client_spec); + file_system.borrow_mut().remove(&client_spec); } /// @param non-empty-string|non-empty-list $command @@ -168,7 +168,8 @@ impl Perforce { _ => vec![], }; self.process - .execute(&cmd_vec, &mut self.command_result, None) + .borrow_mut() + .execute_args(&cmd_vec, &mut self.command_result, None) } pub fn get_client(&mut self) -> String { @@ -195,7 +196,7 @@ impl Perforce { pub fn initialize_path(&mut self, path: &str) { self.path = path.to_string(); let fs = self.get_filesystem(); - fs.ensure_directory_exists(path); + fs.borrow_mut().ensure_directory_exists(path); } pub(crate) fn get_port(&self) -> &str { @@ -361,7 +362,7 @@ impl Perforce { .collect(), )); if exit_code != 0 { - let error_output = self.process.get_error_output().to_string(); + let error_output = self.process.borrow().get_error_output().to_string(); let user = self.get_user().unwrap_or_default(); let index = strpos(&error_output, &user); if index.is_none() { @@ -401,7 +402,7 @@ impl Perforce { file_get_contents(&self.get_p4_client_spec()), None, ); - process.run(None, IndexMap::new()); + process.run(None); } pub fn sync_code_base(&mut self, source_reference: Option<&str>) -> Result<()> { @@ -558,7 +559,7 @@ impl Perforce { None, ); - process.run(None, IndexMap::new()) + process.run(None) } pub fn p4_login(&mut self) -> Result<()> { @@ -583,11 +584,14 @@ impl Perforce { password, None, ); - process.run(None, IndexMap::new()); + process.run(None); if !process.is_successful() { return Err(Exception { - message: format!("Error logging in:{}", self.process.get_error_output()), + message: format!( + "Error logging in:{}", + self.process.borrow().get_error_output() + ), code: 0, } .into()); @@ -847,15 +851,17 @@ impl Perforce { Some(self.command_result.clone()) } - pub fn get_filesystem(&mut self) -> &Filesystem { + pub fn get_filesystem(&mut self) -> &std::rc::Rc> { if self.filesystem.is_none() { - self.filesystem = Some(Filesystem::new(&self.process)); + self.filesystem = Some(std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new( + Some(std::rc::Rc::clone(&self.process)), + )))); } self.filesystem.as_ref().unwrap() } - pub fn set_filesystem(&mut self, fs: Filesystem) { + pub fn set_filesystem(&mut self, fs: std::rc::Rc>) { self.filesystem = Some(fs); } diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs index ed1958d..d0410ff 100644 --- a/crates/shirabe/src/util/process_executor.rs +++ b/crates/shirabe/src/util/process_executor.rs @@ -5,7 +5,7 @@ use anyhow::Result; use indexmap::IndexMap; use std::sync::{LazyLock, Mutex}; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_external_packages::react::promise::promise::Promise; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; use shirabe_external_packages::seld::signal::signal_handler::SignalHandler; @@ -79,7 +79,7 @@ impl ProcessExecutor { const GIT_CMDS_NEED_GIT_DIR: &'static [&'static [&'static str]] = &[&["show"], &["log"], &["branch"], &["remote", "set-url"]]; - pub fn new(io: Option>, _: Option<()>) -> Self { + pub fn new(io: Option>) -> Self { let mut this = Self { capture_output: false, error_output: String::new(), @@ -116,6 +116,28 @@ impl ProcessExecutor { self.do_execute(command, cwd, false, None) } + /// Convenience wrapper used by phase-A code that calls + /// `process.execute(&[String], &mut String, Option<&str>) == 0`. + /// Forwards to `execute`, returning the status code (0 on Err for compatibility). + pub fn execute_args>( + &mut self, + command: &[String], + output: &mut String, + cwd: Option, + ) -> i64 { + let cmd = PhpMixed::List( + command + .iter() + .map(|s| Box::new(PhpMixed::String(s.clone()))) + .collect(), + ); + let mut buf = PhpMixed::String(String::new()); + let cwd_str: Option<&str> = cwd.as_ref().map(|s| s.as_ref()); + let rc = self.execute(cmd, Some(&mut buf), cwd_str).unwrap_or(1); + *output = buf.as_string().unwrap_or("").to_string(); + rc + } + /// runs a process on the commandline in TTY mode pub fn execute_tty(&mut self, command: PhpMixed, cwd: Option<&str>) -> Result { if Platform::is_tty(None) { @@ -142,14 +164,16 @@ impl ProcessExecutor { if is_string(&command) { let mut command_str = command.as_string().unwrap_or("").to_string(); if Platform::is_windows() { - if let Some(m) = Preg::is_match_strict_groups(r"{^([^:/\\]++) }", &command_str) { + let mut m: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3(r"{^([^:/\\]++) }", &command_str, Some(&mut m)) + .unwrap_or(false) + { + let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); command_str = substr_replace( &command_str, - &Self::escape(PhpMixed::String(Self::get_executable( - m.get(1).cloned().unwrap_or_default().as_str(), - ))), + &Self::escape(PhpMixed::String(Self::get_executable(&m1))), 0, - strlen(m.get(1).cloned().unwrap_or_default().as_str()) as usize, + strlen(&m1) as usize, ); } } @@ -359,17 +383,15 @@ impl ProcessExecutor { } if Process::ERR == r#type { - self.io.as_ref().unwrap().write_error_raw( - PhpMixed::String(buffer.to_string()), - false, - io_interface::NORMAL, - ); + self.io + .as_mut() + .unwrap() + .write_error_raw3(buffer, false, io_interface::NORMAL); } else { - self.io.as_ref().unwrap().write_raw( - PhpMixed::String(buffer.to_string()), - false, - io_interface::NORMAL, - ); + self.io + .as_mut() + .unwrap() + .write_raw3(buffer, false, io_interface::NORMAL); } } @@ -600,13 +622,17 @@ impl ProcessExecutor { if Preg::is_match( GitHub::GITHUB_TOKEN_REGEX, m.get("user").cloned().unwrap_or_default().as_str(), - ) { + ) + .unwrap_or(false) + { return "://***:***@".to_string(); } if Preg::is_match( r"{^[a-f0-9]{12,}$}", m.get("user").cloned().unwrap_or_default().as_str(), - ) { + ) + .unwrap_or(false) + { return "://***:***@".to_string(); } @@ -668,7 +694,7 @@ impl ProcessExecutor { // PHP: Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes) argument = Preg::replace_with_count(r#"/(\\*)"/"#, r#"$1$1\""#, &argument, -1, &mut dquotes); - let meta = dquotes > 0 || Preg::is_match(r"/%[^%]+%|![^!]+!/", &argument); + let meta = dquotes > 0 || Preg::is_match(r"/%[^%]+%|![^!]+!/", &argument).unwrap_or(false); if !meta && !quote { quote = strpbrk(&argument, "^&|<>()").is_some(); diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs index d2658fe..7c10640 100644 --- a/crates/shirabe/src/util/remote_filesystem.rs +++ b/crates/shirabe/src/util/remote_filesystem.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ FILTER_VALIDATE_BOOLEAN, PHP_URL_HOST, PHP_URL_PATH, PHP_VERSION_ID, PhpMixed, RuntimeException, array_replace_recursive, base64_encode, explode, extension_loaded, @@ -33,7 +33,7 @@ pub enum GetResult { #[derive(Debug)] pub struct RemoteFilesystem { io: Box, - config: Config, + config: std::rc::Rc>, scheme: String, bytes_max: i64, origin_url: String, @@ -55,7 +55,7 @@ pub struct RemoteFilesystem { impl RemoteFilesystem { pub fn new( io: Box, - config: Config, + config: std::rc::Rc>, options: IndexMap, disable_tls: bool, auth_helper: Option, @@ -140,8 +140,14 @@ impl RemoteFilesystem { pub fn find_status_code(headers: &[String]) -> Option { let mut value: Option = None; for header in headers { - if let Ok(Some(m)) = Preg::is_match_strict_groups("{^HTTP/\\S+ (\\d+)}i", header) { - value = Some(m["1"].parse().unwrap_or(0)); + let mut m: IndexMap = IndexMap::new(); + if Preg::is_match_strict_groups3("{^HTTP/\\S+ (\\d+)}i", header, Some(&mut m)) + .unwrap_or(false) + { + value = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .or(Some(0)); } } @@ -251,8 +257,8 @@ impl RemoteFilesystem { let proxy = ProxyManager::get_instance().get_proxy_for_request(&file_url); let using_proxy = proxy.get_status(" using proxy (%s)"); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "{}{}{}", if strpos(&orig_file_url, "http") == Some(0) { "Downloading " @@ -261,7 +267,7 @@ impl RemoteFilesystem { }, Url::sanitize(&orig_file_url), using_proxy - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -270,12 +276,16 @@ impl RemoteFilesystem { || (strpos(&file_url, "$").is_none() && strpos(&file_url, "%24").is_none())) && !degraded_packagist { - self.config.prohibit_url_by_config(&file_url, &*self.io); + let _ = self.config.borrow_mut().prohibit_url_by_config( + &file_url, + Some(&*self.io), + &indexmap::IndexMap::new(), + ); } if self.progress && !is_redirect { - self.io.write_error( - PhpMixed::String("Downloading (connecting...)".to_string()), + self.io.write_error3( + "Downloading (connecting...)", false, crate::io::io_interface::NORMAL, ); @@ -344,13 +354,13 @@ impl RemoteFilesystem { .unwrap_or_else(|_| self.normalize_result(result.as_deref())); e.set_response(decoded); - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "Content-Length mismatch, received {} out of {} bytes: ({})", Platform::strlen(result.as_deref().unwrap_or("")), cl_int, base64_encode(result.as_deref().unwrap_or("")) - )), + ), true, crate::io::io_interface::DEBUG, ); @@ -395,22 +405,15 @@ impl RemoteFilesystem { let msg_owned = format!("{}", e); if !self.degraded_mode && strpos(&msg_owned, "Operation timed out").is_some() { self.degraded_mode = true; - self.io.write_error( - PhpMixed::String("".to_string()), - true, - crate::io::io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String(format!("{}", msg_owned))), - Box::new(PhpMixed::String( - "Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info" - .to_string(), - )), - ]), - true, - crate::io::io_interface::NORMAL, - ); + self.io + .write_error3("", true, crate::io::io_interface::NORMAL); + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String(format!("{}", msg_owned))), + Box::new(PhpMixed::String( + "Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info" + .to_string(), + )), + ]), true, crate::io::io_interface::NORMAL); return self.get( &self.origin_url.clone(), @@ -460,6 +463,7 @@ impl RemoteFilesystem { let gitlab_domains: Vec = self .config + .borrow_mut() .get("gitlab-domains") .and_then(|v| v.as_list()) .map(|l| { @@ -494,8 +498,8 @@ impl RemoteFilesystem { if code >= 400 && code <= 599 { if !self.retry { if self.progress && !is_redirect { - self.io.overwrite_error( - PhpMixed::String("Downloading (failed)".to_string()), + self.io.overwrite_error4( + "Downloading (failed)", false, None, crate::io::io_interface::NORMAL, @@ -522,15 +526,15 @@ impl RemoteFilesystem { } if self.progress && !self.retry && !is_redirect { - self.io.overwrite_error( - PhpMixed::String(format!( + self.io.overwrite_error4( + &format!( "Downloading ({})", if result.is_none() { "failed" } else { "100%" } - )), + ), false, None, crate::io::io_interface::NORMAL, @@ -552,21 +556,17 @@ impl RemoteFilesystem { } self.degraded_mode = true; - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String("".to_string())), - Box::new(PhpMixed::String(format!( - "Failed to decode response: {}", - e - ))), - Box::new(PhpMixed::String( - "Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info" - .to_string(), - )), - ]), - true, - crate::io::io_interface::NORMAL, - ); + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String("".to_string())), + Box::new(PhpMixed::String(format!( + "Failed to decode response: {}", + e + ))), + Box::new(PhpMixed::String( + "Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info" + .to_string(), + )), + ]), true, crate::io::io_interface::NORMAL); return self.get( &self.origin_url.clone(), @@ -640,22 +640,15 @@ impl RemoteFilesystem { let msg_owned = format!("{}", e); if !self.degraded_mode && strpos(&msg_owned, "Operation timed out").is_some() { self.degraded_mode = true; - self.io.write_error( - PhpMixed::String("".to_string()), - true, - crate::io::io_interface::NORMAL, - ); - self.io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String(format!("{}", msg_owned))), - Box::new(PhpMixed::String( - "Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info" - .to_string(), - )), - ]), - true, - crate::io::io_interface::NORMAL, - ); + self.io + .write_error3("", true, crate::io::io_interface::NORMAL); + self.io.write_error3(PhpMixed::List(vec![ + Box::new(PhpMixed::String(format!("{}", msg_owned))), + Box::new(PhpMixed::String( + "Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info" + .to_string(), + )), + ]), true, crate::io::io_interface::NORMAL); return self.get( &self.origin_url.clone(), @@ -771,11 +764,8 @@ impl RemoteFilesystem { && Some(progression) != self.last_progress { self.last_progress = Some(progression); - self.io.overwrite_error( - PhpMixed::String(format!( - "Downloading ({}%)", - progression - )), + self.io.overwrite_error4( + &format!("Downloading ({}%)", progression), false, None, crate::io::io_interface::NORMAL, @@ -944,19 +934,16 @@ impl RemoteFilesystem { if let Some(target_url) = target_url { self.redirects += 1; - self.io.write_error( - PhpMixed::String("".to_string()), - true, - crate::io::io_interface::DEBUG, - ); - self.io.write_error( - PhpMixed::String(sprintf( + self.io + .write_error3("", true, crate::io::io_interface::DEBUG); + self.io.write_error3( + &sprintf( "Following redirect (%u) %s", &[ PhpMixed::Int(self.redirects), PhpMixed::String(Url::sanitize(&target_url)), ], - )), + ), true, crate::io::io_interface::DEBUG, ); diff --git a/crates/shirabe/src/util/svn.rs b/crates/shirabe/src/util/svn.rs index 955e28c..107be1b 100644 --- a/crates/shirabe/src/util/svn.rs +++ b/crates/shirabe/src/util/svn.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ LogicException, PHP_URL_HOST, PhpMixed, RuntimeException, empty, implode, parse_url, parse_url_all, stripos, strpos, trim, @@ -35,11 +35,11 @@ pub struct Svn { /// @var bool pub(crate) cache_credentials: bool, /// @var ProcessExecutor - pub(crate) process: ProcessExecutor, + pub(crate) process: std::rc::Rc>, /// @var int pub(crate) qty_auth_tries: i64, /// @var Config - pub(crate) config: Config, + pub(crate) config: std::rc::Rc>, } /// @var string|null @@ -51,10 +51,12 @@ impl Svn { pub fn new( url: String, io: Box, - config: Config, - process: Option, + config: std::rc::Rc>, + process: Option>>, ) -> Self { - let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); + let process = process.unwrap_or_else(|| { + std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(&*io))) + }); Self { url, io, @@ -91,7 +93,11 @@ impl Svn { verbose: bool, ) -> Result { // Ensure we are allowed to use this URL by config - self.config.prohibit_url_by_config(url, &*self.io)?; + self.config.borrow_mut().prohibit_url_by_config( + url, + Some(&*self.io), + &indexmap::IndexMap::new(), + )?; self.execute_with_auth_retry(command, cwd, url, path, verbose) .map(|o| o.unwrap_or_default()) @@ -149,14 +155,16 @@ impl Svn { }; // TODO(phase-b): pass handler callback to process.execute let mut handler_output = String::new(); - let status = self - .process - .execute(&command, &mut handler_output, cwd.map(String::from)); + let status = self.process.borrow_mut().execute_args( + &command, + &mut handler_output, + cwd.map(String::from), + ); if 0 == status { return Ok(output); } - let error_output = self.process.get_error_output(); + let error_output = self.process.borrow().get_error_output(); let full_output = trim( &implode("\n", &[output.clone().unwrap_or_default(), error_output]), None, @@ -341,12 +349,12 @@ impl Svn { /// Create the auth params from the configuration file. fn create_auth_from_config(&mut self) -> bool { - if !self.config.has("http-basic") { + if !self.config.borrow().has("http-basic") { self.has_auth = Some(false); return false; } - let auth_config = self.config.get("http-basic"); + let auth_config = self.config.borrow_mut().get("http-basic"); let host = parse_url(&self.url, PHP_URL_HOST); let host_str = host.as_string().unwrap_or(""); @@ -419,16 +427,21 @@ impl Svn { let mut cached = VERSION.lock().unwrap(); if cached.is_none() { let mut output = String::new(); - if 0 == self.process.execute( + if 0 == self.process.borrow_mut().execute_args( &["svn".to_string(), "--version".to_string()], &mut output, None, ) { - // TODO(phase-b): Preg::is_match with captures should populate $match - if let Ok(Some(matches)) = - Preg::is_match_with_indexed_captures(r"{(\d+(?:\.\d+)+)}", &output) + let mut matches: IndexMap = IndexMap::new(); + if Preg::is_match3(r"{(\d+(?:\.\d+)+)}", &output, Some(&mut matches)) + .unwrap_or(false) { - *cached = Some(matches.get(1).cloned().unwrap_or_default()); + *cached = Some( + matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(), + ); } } } diff --git a/crates/shirabe/src/util/sync_helper.rs b/crates/shirabe/src/util/sync_helper.rs index 22e4f1f..7d69db3 100644 --- a/crates/shirabe/src/util/sync_helper.rs +++ b/crates/shirabe/src/util/sync_helper.rs @@ -18,9 +18,9 @@ impl<'a> DownloaderOrManager<'a> { package: &dyn PackageInterface, path: &str, prev_package: Option<&dyn PackageInterface>, - ) -> Box { + ) -> Result> { match self { - Self::Interface(d) => d.download(package, path, prev_package), + Self::Interface(d) => d.download3(package, path, prev_package), Self::Manager(d) => d.borrow().download(package, path, prev_package), } } @@ -31,16 +31,20 @@ impl<'a> DownloaderOrManager<'a> { package: &dyn PackageInterface, path: &str, prev_package: Option<&dyn PackageInterface>, - ) -> Box { + ) -> Result> { match self { Self::Interface(d) => d.prepare(r#type, package, path, prev_package), Self::Manager(d) => d.borrow().prepare(r#type, package, path, prev_package), } } - fn install(&self, package: &dyn PackageInterface, path: &str) -> Box { + fn install( + &self, + package: &dyn PackageInterface, + path: &str, + ) -> Result> { match self { - Self::Interface(d) => d.install(package, path), + Self::Interface(d) => d.install2(package, path), Self::Manager(d) => d.borrow().install(package, path), } } @@ -50,7 +54,7 @@ impl<'a> DownloaderOrManager<'a> { package: &dyn PackageInterface, prev_package: &dyn PackageInterface, path: &str, - ) -> Box { + ) -> Result> { match self { Self::Interface(d) => d.update(package, prev_package, path), Self::Manager(d) => d.borrow().update(package, prev_package, path), @@ -63,7 +67,7 @@ impl<'a> DownloaderOrManager<'a> { package: &dyn PackageInterface, path: &str, prev_package: Option<&dyn PackageInterface>, - ) -> Box { + ) -> Result> { match self { Self::Interface(d) => d.cleanup(r#type, package, path, prev_package), Self::Manager(d) => d.borrow().cleanup(r#type, package, path, prev_package), @@ -90,18 +94,18 @@ impl SyncHelper { let result: Result<()> = (|| { Self::r#await( r#loop, - Some(downloader.download(package, &path, prev_package)), + Some(downloader.download(package, &path, prev_package)?), )?; Self::r#await( r#loop, - Some(downloader.prepare(r#type, package, &path, prev_package)), + Some(downloader.prepare(r#type, package, &path, prev_package)?), )?; if r#type == "update" { if let Some(prev) = prev_package { - Self::r#await(r#loop, Some(downloader.update(package, prev, &path)))?; + Self::r#await(r#loop, Some(downloader.update(package, prev, &path)?))?; } } else { - Self::r#await(r#loop, Some(downloader.install(package, &path)))?; + Self::r#await(r#loop, Some(downloader.install(package, &path)?))?; } Ok(()) })(); @@ -109,14 +113,14 @@ impl SyncHelper { if result.is_err() { Self::r#await( r#loop, - Some(downloader.cleanup(r#type, package, &path, prev_package)), + Some(downloader.cleanup(r#type, package, &path, prev_package)?), )?; return result; } Self::r#await( r#loop, - Some(downloader.cleanup(r#type, package, &path, prev_package)), + Some(downloader.cleanup(r#type, package, &path, prev_package)?), )?; Ok(()) } diff --git a/crates/shirabe/src/util/url.rs b/crates/shirabe/src/util/url.rs index 7d0ab21..7dc6e4f 100644 --- a/crates/shirabe/src/util/url.rs +++ b/crates/shirabe/src/util/url.rs @@ -2,7 +2,8 @@ use crate::config::Config; use crate::util::github::GitHub; -use shirabe_external_packages::composer::pcre::preg::Preg; +use indexmap::IndexMap; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{PHP_URL_HOST, PHP_URL_PORT, PhpMixed, in_array, parse_url}; pub struct Url; @@ -15,62 +16,80 @@ impl Url { .unwrap_or_default(); if host == "api.github.com" || host == "github.com" || host == "www.github.com" { - if let Some(m) = Preg::match_( + let mut m: IndexMap = IndexMap::new(); + if Preg::match3( r"(?i)^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$", &url, - ) { + Some(&mut m), + ) + .unwrap_or(false) + { url = format!( "https://api.github.com/repos/{}/{}/{}ball/{}", - m.get("1").unwrap_or(&String::new()), - m.get("2").unwrap_or(&String::new()), - m.get("3").unwrap_or(&String::new()), + m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(), r#ref ); - } else if let Some(m) = Preg::match_( + } else if Preg::match3( r"(?i)^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$", &url, - ) { + Some(&mut m), + ) + .unwrap_or(false) + { url = format!( "https://api.github.com/repos/{}/{}/{}ball/{}", - m.get("1").unwrap_or(&String::new()), - m.get("2").unwrap_or(&String::new()), - m.get("3").unwrap_or(&String::new()), + m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(), r#ref ); - } else if let Some(m) = Preg::match_( + } else if Preg::match3( r"(?i)^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$", &url, - ) { + Some(&mut m), + ) + .unwrap_or(false) + { url = format!( "https://api.github.com/repos/{}/{}/{}ball/{}", - m.get("1").unwrap_or(&String::new()), - m.get("2").unwrap_or(&String::new()), - m.get("3").unwrap_or(&String::new()), + m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(), r#ref ); } } else if host == "bitbucket.org" || host == "www.bitbucket.org" { - if let Some(m) = Preg::match_( + let mut m: IndexMap = IndexMap::new(); + if Preg::match3( r"(?i)^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$", &url, - ) { + Some(&mut m), + ) + .unwrap_or(false) + { url = format!( "https://bitbucket.org/{}/{}/get/{}.{}", - m.get("1").unwrap_or(&String::new()), - m.get("2").unwrap_or(&String::new()), + m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(), r#ref, - m.get("4").unwrap_or(&String::new()) + m.get(&CaptureKey::ByIndex(4)).cloned().unwrap_or_default() ); } } else if host == "gitlab.com" || host == "www.gitlab.com" { - if let Some(m) = Preg::match_( + let mut m: IndexMap = IndexMap::new(); + if Preg::match3( r"(?i)^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$", &url, - ) { + Some(&mut m), + ) + .unwrap_or(false) + { url = format!( "https://gitlab.com/api/v4/projects/{}/repository/archive.{}?sha={}", - m.get("1").unwrap_or(&String::new()), - m.get("2").unwrap_or(&String::new()), + m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(), r#ref ); } @@ -82,8 +101,9 @@ impl Url { url = Preg::replace( r"(?i)(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$", &format!("$1/{}", r#ref), - url, - ); + &url, + ) + .unwrap_or(url); } else if in_array( PhpMixed::String(host.clone()), &config.get("gitlab-domains"), @@ -92,8 +112,9 @@ impl Url { url = Preg::replace( r"(?i)(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$", &format!("${{1}}{}", r#ref), - url, - ); + &url, + ) + .unwrap_or(url); } assert!(!url.is_empty()); @@ -148,30 +169,29 @@ impl Url { pub fn sanitize(url: String) -> String { // GitHub repository rename result in redirect locations containing the access_token as GET parameter // e.g. https://api.github.com/repositories/9999999999?access_token=github_token - let url = Preg::replace(r"([&?]access_token=)[^&]+", "$1***", url); + let url = Preg::replace(r"([&?]access_token=)[^&]+", "$1***", &url).unwrap_or(url); let url = Preg::replace_callback( r"(?i)^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@", |m| { + let user = m + .get(&CaptureKey::ByName("user".to_string())) + .cloned() + .unwrap_or_default(); + let prefix = m + .get(&CaptureKey::ByName("prefix".to_string())) + .cloned() + .unwrap_or_default(); // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that - if Preg::is_match( - GitHub::GITHUB_TOKEN_REGEX, - m.get("user").map(|s| s.as_str()).unwrap_or(""), - ) { - format!( - "{}***:***@", - m.get("prefix").map(|s| s.as_str()).unwrap_or("") - ) + if Preg::is_match(GitHub::GITHUB_TOKEN_REGEX, &user).unwrap_or(false) { + format!("{}***:***@", prefix) } else { - format!( - "{}{}:***@", - m.get("prefix").map(|s| s.as_str()).unwrap_or(""), - m.get("user").map(|s| s.as_str()).unwrap_or("") - ) + format!("{}{}:***@", prefix, user) } }, - url, - ); + &url, + ) + .unwrap_or(url); url } -- cgit v1.3.1