aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-04 13:59:49 +0900
committernsfisis <nsfisis@gmail.com>2026-05-04 13:59:49 +0900
commite6e4f6f6319b39ba3020f96d070b637054c04b6a (patch)
tree216423fc61716825f1eded1163e17264dbff3ea1 /crates/mozart/src
parent30b222e7324e933f1e1bde5c5e34d588e36adbaa (diff)
downloadphp-mozart-e6e4f6f6319b39ba3020f96d070b637054c04b6a.tar.gz
php-mozart-e6e4f6f6319b39ba3020f96d070b637054c04b6a.tar.zst
php-mozart-e6e4f6f6319b39ba3020f96d070b637054c04b6a.zip
feat(http): honor config.cafile and config.capath
Composer's config.cafile/config.capath were accepted by the config command but ignored by every HTTP request. Centralize reqwest client construction in mozart_core::http, pre-load the configured CA bundle at startup, and route every callsite (registry, vcs drivers, diagnose, self-update) through the shared builder so user-supplied roots are actually used during HTTPS verification.
Diffstat (limited to 'crates/mozart/src')
-rw-r--r--crates/mozart/src/commands.rs6
-rw-r--r--crates/mozart/src/commands/config_helpers.rs47
-rw-r--r--crates/mozart/src/commands/diagnose.rs6
-rw-r--r--crates/mozart/src/commands/self_update.rs6
4 files changed, 57 insertions, 8 deletions
diff --git a/crates/mozart/src/commands.rs b/crates/mozart/src/commands.rs
index c648754..504e38d 100644
--- a/crates/mozart/src/commands.rs
+++ b/crates/mozart/src/commands.rs
@@ -254,6 +254,12 @@ pub async fn execute(cli: &Cli) -> anyhow::Result<()> {
cli.no_ansi,
cli.no_interaction,
);
+
+ // Initialize HTTPS root certificates from `config.cafile` / `config.capath`
+ // before any command makes a network request.
+ let tls_opts = config_helpers::load_tls_options(cli);
+ mozart_core::http::init_tls_options(&tls_opts)?;
+
let command = cli.command.as_ref().expect("command must be set");
match command {
Commands::About(args) => about::execute(args, cli, &console).await,
diff --git a/crates/mozart/src/commands/config_helpers.rs b/crates/mozart/src/commands/config_helpers.rs
index 9b60129..422db4d 100644
--- a/crates/mozart/src/commands/config_helpers.rs
+++ b/crates/mozart/src/commands/config_helpers.rs
@@ -68,6 +68,53 @@ pub(crate) fn working_dir(cli: &super::Cli) -> anyhow::Result<PathBuf> {
}
}
+/// Read TLS-related options (`config.cafile`, `config.capath`) from the merged
+/// global + local config. Local values override global. Relative paths are
+/// resolved against the directory of the config file that defined them.
+pub(crate) fn load_tls_options(cli: &super::Cli) -> mozart_core::http::TlsOptions {
+ let mut opts = mozart_core::http::TlsOptions::default();
+
+ let home = composer_home();
+ apply_tls_from_file(&home.join("config.json"), &home, &mut opts);
+
+ if let Ok(wd) = working_dir(cli) {
+ apply_tls_from_file(&wd.join("composer.json"), &wd, &mut opts);
+ }
+
+ opts
+}
+
+fn apply_tls_from_file(path: &Path, base_dir: &Path, opts: &mut mozart_core::http::TlsOptions) {
+ let Ok(content) = std::fs::read_to_string(path) else {
+ return;
+ };
+ let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) else {
+ return;
+ };
+ let Some(cfg) = json.get("config").and_then(|v| v.as_object()) else {
+ return;
+ };
+ if let Some(s) = cfg.get("cafile").and_then(|v| v.as_str())
+ && !s.is_empty()
+ {
+ opts.cafile = Some(resolve_relative(s, base_dir));
+ }
+ if let Some(s) = cfg.get("capath").and_then(|v| v.as_str())
+ && !s.is_empty()
+ {
+ opts.capath = Some(resolve_relative(s, base_dir));
+ }
+}
+
+fn resolve_relative(path: &str, base: &Path) -> PathBuf {
+ let p = Path::new(path);
+ if p.is_absolute() {
+ p.to_path_buf()
+ } else {
+ base.join(p)
+ }
+}
+
/// Read a JSON file as `serde_json::Value`.
/// If the file does not exist, return a default skeleton:
/// `{"config": {}}` for global files, `{}` for local.
diff --git a/crates/mozart/src/commands/diagnose.rs b/crates/mozart/src/commands/diagnose.rs
index bb6e886..2320ddc 100644
--- a/crates/mozart/src/commands/diagnose.rs
+++ b/crates/mozart/src/commands/diagnose.rs
@@ -92,9 +92,8 @@ async fn check_http_connectivity(url: &str) -> CheckResult {
return CheckResult::Skip("COMPOSER_DISABLE_NETWORK is set".to_string());
}
- let client = match reqwest::Client::builder()
+ let client = match mozart_core::http::client_builder()
.timeout(std::time::Duration::from_secs(10))
- .user_agent(mozart_core::http::user_agent())
.build()
{
Ok(c) => c,
@@ -120,9 +119,8 @@ async fn check_github_api() -> CheckResult {
return CheckResult::Skip("COMPOSER_DISABLE_NETWORK is set".to_string());
}
- let client = match reqwest::Client::builder()
+ let client = match mozart_core::http::client_builder()
.timeout(std::time::Duration::from_secs(10))
- .user_agent(mozart_core::http::user_agent())
.build()
{
Ok(c) => c,
diff --git a/crates/mozart/src/commands/self_update.rs b/crates/mozart/src/commands/self_update.rs
index afc77f3..2c7c59b 100644
--- a/crates/mozart/src/commands/self_update.rs
+++ b/crates/mozart/src/commands/self_update.rs
@@ -150,9 +150,8 @@ fn version_from_backup(path: &Path) -> String {
async fn fetch_releases(include_prerelease: bool) -> anyhow::Result<Vec<GitHubRelease>> {
let url = format!("{GITHUB_API_BASE}/{GITHUB_REPO}/releases");
- let client = reqwest::Client::builder()
+ let client = mozart_core::http::client_builder()
.timeout(std::time::Duration::from_secs(30))
- .user_agent(mozart_core::http::user_agent())
.build()
.map_err(|e| anyhow::anyhow!("Could not build HTTP client: {e}"))?;
@@ -230,9 +229,8 @@ async fn download_asset(
show_progress: bool,
console: &mozart_core::console::Console,
) -> anyhow::Result<()> {
- let client = reqwest::Client::builder()
+ let client = mozart_core::http::client_builder()
.timeout(std::time::Duration::from_secs(300))
- .user_agent(mozart_core::http::user_agent())
.build()
.map_err(|e| anyhow::anyhow!("Could not build HTTP client: {e}"))?;