diff --git a/Cargo.lock b/Cargo.lock index c2b4ae58..b16dae0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -660,6 +660,16 @@ dependencies = [ "log", ] +[[package]] +name = "env_proxy" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5019be18538406a43b5419a5501461f0c8b49ea7dfda0cfc32f4e51fc44be1" +dependencies = [ + "log", + "url", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2172,6 +2182,15 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + [[package]] name = "rustls-webpki" version = "0.100.2" @@ -2906,6 +2925,7 @@ dependencies = [ "codespan-reporting", "comemo", "dirs", + "env_proxy", "flate2", "inferno", "memmap2", @@ -2913,6 +2933,8 @@ dependencies = [ "once_cell", "open", "pathdiff 0.1.0", + "rustls", + "rustls-pemfile", "same-file", "self-replace", "semver", diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 8396e97b..4b167651 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -49,6 +49,9 @@ tracing-error = "0.2" tracing-flame = "0.2.0" tracing-subscriber = "0.3.17" ureq = "2" +rustls = "0.21" +rustls-pemfile = "1" +env_proxy = "0.4" walkdir = "2" xz2 = { version = "0.1", optional = true } zip = { version = "0.6", optional = true } diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index 24a843fb..2113e03c 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -17,6 +17,10 @@ pub struct CliArguments { /// -v = warning & error, -vv = info, -vvv = debug, -vvvv = trace #[clap(short, long, action = ArgAction::Count)] pub verbosity: u8, + + /// Path to a custom CA certificate to use when making network requests. + #[clap(long = "cert", env = "TYPST_CERT")] + pub cert: Option, } /// What to do. diff --git a/crates/typst-cli/src/download.rs b/crates/typst-cli/src/download.rs index 416f8c68..9db73fe8 100644 --- a/crates/typst-cli/src/download.rs +++ b/crates/typst-cli/src/download.rs @@ -1,20 +1,60 @@ -use std::collections::VecDeque; -use std::io::{self, ErrorKind, Read, Stderr, Write}; -use std::time::{Duration, Instant}; - -use ureq::Response; - // Acknowledgement: // Closely modelled after rustup's [`DownloadTracker`]. // https://github.com/rust-lang/rustup/blob/master/src/cli/download_tracker.rs +use std::collections::VecDeque; +use std::io::{self, ErrorKind, Read, Stderr, Write}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use once_cell::sync::Lazy; +use ureq::Response; + /// Keep track of this many download speed samples. const SPEED_SAMPLES: usize = 5; +/// Lazily loads a custom CA certificate if present, but if there's an error +/// loading certificate, it just uses the default configuration. +static TLS_CONFIG: Lazy>> = Lazy::new(|| { + crate::ARGS + .cert + .as_ref() + .map(|path| { + let file = std::fs::OpenOptions::new().read(true).open(path)?; + let mut buffer = std::io::BufReader::new(file); + let certs = rustls_pemfile::certs(&mut buffer)?; + let mut store = rustls::RootCertStore::empty(); + store.add_parsable_certificates(&certs); + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(store) + .with_no_client_auth(); + Ok::<_, std::io::Error>(Arc::new(config)) + }) + .and_then(|x| x.ok()) +}); + /// Download binary data and display its progress. #[allow(clippy::result_large_err)] pub fn download_with_progress(url: &str) -> Result, ureq::Error> { - let response = ureq::get(url).call()?; + let mut builder = ureq::AgentBuilder::new() + .user_agent(concat!("typst/{}", env!("CARGO_PKG_VERSION"))); + + // Get the network proxy config from the environment. + if let Some(proxy) = env_proxy::for_url_str(url) + .to_url() + .and_then(|url| ureq::Proxy::new(url).ok()) + { + builder = builder.proxy(proxy); + } + + // Apply a custom CA certificate if present. + if let Some(config) = &*TLS_CONFIG { + builder = builder.tls_config(config.clone()); + } + + let agent = builder.build(); + let response = agent.get(url).call()?; Ok(RemoteReader::from_response(response).download()?) } diff --git a/crates/typst-cli/src/fonts.rs b/crates/typst-cli/src/fonts.rs index 3e89e0d6..6b4fc2fc 100644 --- a/crates/typst-cli/src/fonts.rs +++ b/crates/typst-cli/src/fonts.rs @@ -12,7 +12,7 @@ use walkdir::WalkDir; use crate::args::FontsCommand; /// Execute a font listing command. -pub fn fonts(command: FontsCommand) -> StrResult<()> { +pub fn fonts(command: &FontsCommand) -> StrResult<()> { let mut searcher = FontSearcher::new(); searcher.search(&command.font_paths); diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index fe99e029..a6e60e5b 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -17,6 +17,7 @@ use std::process::ExitCode; use clap::Parser; use codespan_reporting::term::{self, termcolor}; +use once_cell::sync::Lazy; use termcolor::{ColorChoice, WriteColor}; use crate::args::{CliArguments, Command}; @@ -26,10 +27,12 @@ thread_local! { static EXIT: Cell = Cell::new(ExitCode::SUCCESS); } +/// The parsed commandline arguments. +static ARGS: Lazy = Lazy::new(CliArguments::parse); + /// Entry point. fn main() -> ExitCode { - let arguments = CliArguments::parse(); - let _guard = match crate::tracing::setup_tracing(&arguments) { + let _guard = match crate::tracing::setup_tracing(&ARGS) { Ok(guard) => guard, Err(err) => { eprintln!("failed to initialize tracing {}", err); @@ -37,9 +40,9 @@ fn main() -> ExitCode { } }; - let res = match arguments.command { - Command::Compile(command) => crate::compile::compile(command), - Command::Watch(command) => crate::watch::watch(command), + let res = match &ARGS.command { + Command::Compile(command) => crate::compile::compile(command.clone()), + Command::Watch(command) => crate::watch::watch(command.clone()), Command::Query(command) => crate::query::query(command), Command::Fonts(command) => crate::fonts::fonts(command), Command::Update(command) => crate::update::update(command), @@ -89,7 +92,7 @@ mod update { use crate::args::UpdateCommand; use typst::diag::{bail, StrResult}; - pub fn update(_: UpdateCommand) -> StrResult<()> { + pub fn update(_: &UpdateCommand) -> StrResult<()> { bail!( "self-updating is not enabled for this executable, \ please update with the package manager or mechanism \ diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index bf02f49d..68cf3319 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -12,7 +12,7 @@ use crate::set_failed; use crate::world::SystemWorld; /// Execute a query command. -pub fn query(command: QueryCommand) -> StrResult<()> { +pub fn query(command: &QueryCommand) -> StrResult<()> { let mut world = SystemWorld::new(&command.common)?; tracing::info!("Starting querying"); @@ -27,8 +27,8 @@ pub fn query(command: QueryCommand) -> StrResult<()> { match result { // Retrieve and print query results. Ok(document) => { - let data = retrieve(&world, &command, &document)?; - let serialized = format(data, &command)?; + let data = retrieve(&world, command, &document)?; + let serialized = format(data, command)?; println!("{serialized}"); print_diagnostics(&world, &[], &warnings, command.common.diagnostic_format) .map_err(|_| "failed to print diagnostics")?; diff --git a/crates/typst-cli/src/update.rs b/crates/typst-cli/src/update.rs index b22eb7c5..562d7d2d 100644 --- a/crates/typst-cli/src/update.rs +++ b/crates/typst-cli/src/update.rs @@ -21,7 +21,7 @@ const TYPST_REPO: &str = "typst"; /// Fetches a target release or the latest release (if no version was specified) /// from GitHub, unpacks it and self replaces the current binary with the /// pre-compiled asset from the downloaded release. -pub fn update(command: UpdateCommand) -> StrResult<()> { +pub fn update(command: &UpdateCommand) -> StrResult<()> { if let Some(ref version) = command.version { let current_tag = env!("CARGO_PKG_VERSION").parse().unwrap(); @@ -61,7 +61,7 @@ pub fn update(command: UpdateCommand) -> StrResult<()> { fs::copy(current_exe, &backup_path) .map_err(|err| eco_format!("failed to create backup: {err}"))?; - let release = Release::from_tag(command.version)?; + let release = Release::from_tag(command.version.as_ref())?; if !update_needed(&release)? && !command.force { eprintln!("Already up-to-date."); return Ok(()); @@ -99,7 +99,7 @@ struct Release { impl Release { /// Download the target release, or latest if version is `None`, from the /// Typst repository. - pub fn from_tag(tag: Option) -> StrResult { + pub fn from_tag(tag: Option<&Version>) -> StrResult { let url = match tag { Some(tag) => format!( "https://api.github.com/repos/{}/{}/releases/tags/v{}",