fix(plugins): trust platform certificates
Use the operating system trust store and Windows proxy settings while keeping TLS verification enabled. Add retryable registry errors with copyable diagnostics.\n\nRefs #563
This commit is contained in:
parent
4e25e88c31
commit
a717572d65
15 changed files with 434 additions and 44 deletions
216
Cargo.lock
generated
216
Cargo.lock
generated
|
|
@ -233,7 +233,7 @@ dependencies = [
|
|||
"android-properties",
|
||||
"bitflags 2.13.1",
|
||||
"cc",
|
||||
"jni",
|
||||
"jni 0.22.4",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
|
|
@ -919,6 +919,12 @@ dependencies = [
|
|||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
|
|
@ -2595,6 +2601,22 @@ dependencies = [
|
|||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
|
|
@ -3085,7 +3107,7 @@ dependencies = [
|
|||
"dispatch",
|
||||
"futures-channel",
|
||||
"futures-lite",
|
||||
"jni",
|
||||
"jni 0.22.4",
|
||||
"ndk-context",
|
||||
"objc2 0.6.4",
|
||||
"objc2-app-kit 0.3.2",
|
||||
|
|
@ -3765,6 +3787,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.55"
|
||||
|
|
@ -4701,6 +4729,18 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.0"
|
||||
|
|
@ -4710,6 +4750,33 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni 0.21.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
|
|
@ -4799,6 +4866,15 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
|
|
@ -4830,6 +4906,29 @@ version = "4.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "self_cell"
|
||||
version = "1.3.0"
|
||||
|
|
@ -5835,9 +5934,11 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"ureq-proto",
|
||||
"utf8-zero",
|
||||
"webpki-roots",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6187,6 +6288,15 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.8"
|
||||
|
|
@ -6409,7 +6519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||
dependencies = [
|
||||
"windows-core 0.58.0",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6443,7 +6553,7 @@ dependencies = [
|
|||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6536,7 +6646,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6555,7 +6665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result 0.2.0",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6567,13 +6677,22 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6582,7 +6701,7 @@ version = "0.59.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6594,20 +6713,35 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6619,18 +6753,36 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6643,24 +6795,48 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6737,6 +6913,16 @@ dependencies = [
|
|||
"memchr 2.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.31"
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ open = "5"
|
|||
# PDF export / printing. Native-only: printpdf pulls a wasm-incompatible
|
||||
# `memchr` (1.0.2) via lopdf → nom_locate, and the web build has no filesystem.
|
||||
printpdf = { version = "0.9.1", default-features = false }
|
||||
ureq = { version = "3", default-features = false, features = ["rustls"] }
|
||||
ureq = { version = "3", default-features = false, features = ["rustls", "platform-verifier", "win-system-proxy"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# Web gets the dependency-free manifest/ribbon contract only (no plugin host).
|
||||
|
|
|
|||
|
|
@ -524,6 +524,14 @@ pub(super) struct OpenCADStudio {
|
|||
loaded_plugin_ids: rustc_hash::FxHashSet<String>,
|
||||
/// Curated plugin registry fetched from the OpenCADStudio repo.
|
||||
plugin_registry: Vec<crate::plugin::external::RegistryEntry>,
|
||||
/// True while the curated registry request is in flight.
|
||||
plugin_registry_loading: bool,
|
||||
/// Last curated-registry request error. Kept separate from general
|
||||
/// marketplace status so the UI can show a friendly retry card and retain
|
||||
/// copyable technical details.
|
||||
plugin_registry_error: Option<String>,
|
||||
/// Whether the registry error card exposes its raw diagnostic text.
|
||||
plugin_registry_error_details_open: bool,
|
||||
/// User-linked plugin source repos (`owner/repo`) beyond the curated list.
|
||||
plugin_repos: Vec<String>,
|
||||
/// Add-repository text field in the Plugin Manager.
|
||||
|
|
@ -2076,6 +2084,12 @@ pub enum Message {
|
|||
PluginRepoRemove(String),
|
||||
/// The curated registry was fetched.
|
||||
PluginRegistryFetched(Result<Vec<crate::plugin::external::RegistryEntry>, String>),
|
||||
/// Retry the curated registry request after a connection failure.
|
||||
PluginRegistryRetry,
|
||||
/// Expand or collapse raw registry error details.
|
||||
PluginRegistryErrorDetailsToggle,
|
||||
/// Copy registry URL, platform, version, and raw error details.
|
||||
PluginRegistryCopyDiagnostics,
|
||||
/// Patreon supporters fetched at boot for the Start page (name, pledge cents).
|
||||
PatronsFetched(Result<Vec<(String, i64)>, String>),
|
||||
/// Tutorial-playlist videos fetched at boot for the Start page.
|
||||
|
|
@ -2609,6 +2623,9 @@ impl OpenCADStudio {
|
|||
external_plugins: Vec::new(),
|
||||
loaded_plugin_ids: rustc_hash::FxHashSet::default(),
|
||||
plugin_registry: Vec::new(),
|
||||
plugin_registry_loading: false,
|
||||
plugin_registry_error: None,
|
||||
plugin_registry_error_details_open: false,
|
||||
plugin_repos: Vec::new(),
|
||||
plugin_repo_input: String::new(),
|
||||
plugin_search_input: String::new(),
|
||||
|
|
|
|||
|
|
@ -3956,6 +3956,9 @@ impl OpenCADStudio {
|
|||
// Fetch the curated registry and release lists for linked repos.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.plugin_registry_loading = true;
|
||||
self.plugin_registry_error = None;
|
||||
self.plugin_registry_error_details_open = false;
|
||||
let mut tasks = vec![self.fetch_registry_task()];
|
||||
let release_repos: rustc_hash::FxHashSet<String> = self
|
||||
.plugin_repos
|
||||
|
|
@ -4074,6 +4077,9 @@ impl OpenCADStudio {
|
|||
Task::none()
|
||||
}
|
||||
Message::PluginRegistryFetched(Ok(entries)) => {
|
||||
self.plugin_registry_loading = false;
|
||||
self.plugin_registry_error = None;
|
||||
self.plugin_registry_error_details_open = false;
|
||||
// Fetch releases for every curated repo so the dropdowns fill in.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
|
|
@ -4112,7 +4118,41 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
Message::PluginRegistryFetched(Err(e)) => {
|
||||
self.marketplace_status = format!("Registry: {e}");
|
||||
self.plugin_registry_loading = false;
|
||||
self.plugin_registry_error = Some(e);
|
||||
self.plugin_registry_error_details_open = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginRegistryRetry => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.plugin_registry_loading = true;
|
||||
self.plugin_registry_error = None;
|
||||
self.plugin_registry_error_details_open = false;
|
||||
return self.fetch_registry_task();
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginRegistryErrorDetailsToggle => {
|
||||
if self.plugin_registry_error.is_some() {
|
||||
self.plugin_registry_error_details_open =
|
||||
!self.plugin_registry_error_details_open;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginRegistryCopyDiagnostics => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(error) = &self.plugin_registry_error {
|
||||
return iced::clipboard::write(format!(
|
||||
"Open CAD Studio v{}\nOS: {}\nArchitecture: {}\nRegistry: {}\nError: {}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH,
|
||||
crate::plugin::marketplace::REGISTRY_URL,
|
||||
error,
|
||||
));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::PatronsFetched(Ok(names)) => {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ impl OpenCADStudio {
|
|||
&self.loaded_plugin_ids,
|
||||
crate::ui::window::plugin_manager::MarketView {
|
||||
registry: &self.plugin_registry,
|
||||
registry_loading: self.plugin_registry_loading,
|
||||
registry_error: self.plugin_registry_error.as_deref(),
|
||||
registry_error_details_open: self.plugin_registry_error_details_open,
|
||||
input: &self.plugin_repo_input,
|
||||
search: &self.plugin_search_input,
|
||||
repos: &self.plugin_repos,
|
||||
|
|
|
|||
|
|
@ -61,10 +61,7 @@ pub fn load_cached() -> Vec<DiscussionEntry> {
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn fetch_discussions() -> Result<Vec<DiscussionEntry>, String> {
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(15)))
|
||||
.build()
|
||||
.into();
|
||||
let agent = crate::network::agent(std::time::Duration::from_secs(15));
|
||||
let feed = get_text(&agent, FEED_URL)?;
|
||||
let page = get_text(&agent, DISCUSSIONS_URL)?;
|
||||
let pinned = pinned_numbers(&page);
|
||||
|
|
|
|||
|
|
@ -48,10 +48,7 @@ pub async fn check_for_update() -> Option<UpdateInfo> {
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn fetch_latest_if_outdated() -> Option<UpdateInfo> {
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(5)))
|
||||
.build()
|
||||
.into();
|
||||
let agent = crate::network::agent(std::time::Duration::from_secs(5));
|
||||
let body = agent
|
||||
.get(RELEASES_API)
|
||||
.header("User-Agent", concat!("OpenCADStudio/", env!("CARGO_PKG_VERSION")))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ pub mod cli;
|
|||
pub mod command;
|
||||
pub mod entities;
|
||||
pub mod io;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod network;
|
||||
pub mod modules;
|
||||
pub mod patreon;
|
||||
pub mod discussions;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ mod cli;
|
|||
mod command;
|
||||
mod entities;
|
||||
mod io;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod network;
|
||||
mod modules;
|
||||
mod patreon;
|
||||
mod discussions;
|
||||
|
|
|
|||
35
src/network.rs
Normal file
35
src/network.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//! Shared native HTTP client configuration.
|
||||
//!
|
||||
//! Desktop requests use the operating system's certificate verifier so roots
|
||||
//! installed by administrators, corporate proxies, and security software are
|
||||
//! honoured without weakening TLS verification.
|
||||
|
||||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
use std::time::Duration;
|
||||
use ureq::tls::{RootCerts, TlsConfig};
|
||||
|
||||
pub(crate) fn agent(timeout: Duration) -> ureq::Agent {
|
||||
let tls = TlsConfig::builder()
|
||||
.root_certs(RootCerts::PlatformVerifier)
|
||||
.build();
|
||||
ureq::Agent::config_builder()
|
||||
.timeout_global(Some(timeout))
|
||||
.tls_config(tls)
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn agent_uses_platform_verifier_without_disabling_tls() {
|
||||
let agent = agent(Duration::from_secs(1));
|
||||
let tls = agent.config().tls_config();
|
||||
|
||||
assert!(matches!(tls.root_certs(), &RootCerts::PlatformVerifier));
|
||||
assert!(!tls.disable_verification());
|
||||
}
|
||||
}
|
||||
|
|
@ -47,10 +47,7 @@ pub fn fetch_patrons() -> Result<Vec<(String, i64)>, String> {
|
|||
.filter(|t| !t.is_empty())
|
||||
.ok_or("no Patreon token configured")?;
|
||||
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(15)))
|
||||
.build()
|
||||
.into();
|
||||
let agent = crate::network::agent(std::time::Duration::from_secs(15));
|
||||
|
||||
// The token is creator-scoped, so its first campaign is the one to list.
|
||||
let campaigns = get_json(&agent, token, "https://www.patreon.com/api/oauth2/v2/campaigns")?;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use super::external;
|
|||
use super::external::RegistryEntry;
|
||||
|
||||
/// The curated registry, read from the OpenCADStudio repo's `main` branch.
|
||||
const REGISTRY_URL: &str =
|
||||
pub(crate) const REGISTRY_URL: &str =
|
||||
"https://raw.githubusercontent.com/HakanSeven12/OpenCADStudio/main/plugins/registry.json";
|
||||
|
||||
/// Fetch the curated plugin registry (`plugins/registry.json`).
|
||||
|
|
@ -104,10 +104,7 @@ fn external_lib_ext() -> &'static str {
|
|||
}
|
||||
|
||||
fn agent() -> ureq::Agent {
|
||||
ureq::Agent::config_builder()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(15)))
|
||||
.build()
|
||||
.into()
|
||||
crate::network::agent(std::time::Duration::from_secs(15))
|
||||
}
|
||||
|
||||
const UA: &str = concat!("OpenCADStudio/", env!("CARGO_PKG_VERSION"));
|
||||
|
|
|
|||
|
|
@ -434,10 +434,7 @@ fn decode_reference(path: &str) -> Option<DecodedImage> {
|
|||
/// synchronous fetch and would hit CORS on a cross-origin image anyway.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn fetch_remote(url: &str) -> Option<Vec<u8>> {
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(8)))
|
||||
.build()
|
||||
.into();
|
||||
let agent = crate::network::agent(std::time::Duration::from_secs(8));
|
||||
let mut resp = agent
|
||||
.get(url)
|
||||
.header(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ const SCROLLBAR_GUTTER: f32 = 16.0;
|
|||
/// Marketplace state passed to the Plugin Manager view.
|
||||
pub struct MarketView<'a> {
|
||||
pub registry: &'a [RegistryEntry],
|
||||
pub registry_loading: bool,
|
||||
pub registry_error: Option<&'a str>,
|
||||
pub registry_error_details_open: bool,
|
||||
pub input: &'a str,
|
||||
pub search: &'a str,
|
||||
pub repos: &'a [String],
|
||||
|
|
@ -407,12 +410,119 @@ fn add_repository_card<'a>(m: &MarketView) -> Element<'a, Message> {
|
|||
)
|
||||
}
|
||||
|
||||
fn registry_error_message(error: &str) -> (&'static str, &'static str) {
|
||||
let error = error.to_ascii_lowercase();
|
||||
if error.contains("certificate")
|
||||
|| error.contains("unknownissuer")
|
||||
|| error.contains("unknown issuer")
|
||||
{
|
||||
(
|
||||
"Unable to verify the server certificate",
|
||||
"Open CAD Studio could not trust the certificate presented for the plugin registry. \
|
||||
Check your system certificate and proxy settings, then retry.",
|
||||
)
|
||||
} else if error.contains("timed out") || error.contains("timeout") {
|
||||
(
|
||||
"Plugin registry request timed out",
|
||||
"Check your internet or proxy connection, then retry. Manually added repositories \
|
||||
remain available.",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"Unable to load the plugin registry",
|
||||
"Check your internet or proxy connection, then retry. Manually added repositories \
|
||||
remain available.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn registry_notice<'a>(m: &MarketView) -> Option<Element<'a, Message>> {
|
||||
if let Some(error) = m.registry_error {
|
||||
let (title, message) = registry_error_message(error);
|
||||
let actions = row![
|
||||
pill_button(
|
||||
"Retry",
|
||||
Message::PluginRegistryRetry,
|
||||
button::primary,
|
||||
),
|
||||
Space::new().width(6),
|
||||
pill_button(
|
||||
if m.registry_error_details_open {
|
||||
"Hide details"
|
||||
} else {
|
||||
"Show details"
|
||||
},
|
||||
Message::PluginRegistryErrorDetailsToggle,
|
||||
button::secondary,
|
||||
),
|
||||
Space::new().width(6),
|
||||
pill_button(
|
||||
"Copy details",
|
||||
Message::PluginRegistryCopyDiagnostics,
|
||||
button::secondary,
|
||||
),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
let mut body = column![
|
||||
text(title).size(13),
|
||||
text(message).size(11).style(muted_style),
|
||||
Space::new().height(3),
|
||||
actions,
|
||||
]
|
||||
.spacing(5);
|
||||
if m.registry_error_details_open {
|
||||
body = body.push(
|
||||
container(text(error.to_string()).size(10).width(Fill))
|
||||
.padding(8)
|
||||
.width(Fill)
|
||||
.style(container::bordered_box),
|
||||
);
|
||||
}
|
||||
return Some(
|
||||
container(body.padding([10, 12]))
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| {
|
||||
let pair = theme.extended_palette().warning.weak;
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color.scale_alpha(0.16))),
|
||||
border: Border {
|
||||
color: pair.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
(m.registry_loading && m.registry.is_empty()).then(|| {
|
||||
container(
|
||||
column![
|
||||
text("Loading plugin catalog…").size(13),
|
||||
text("Connecting securely using your system certificate settings.")
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(5)
|
||||
.padding([10, 12]),
|
||||
)
|
||||
.width(Fill)
|
||||
.style(container::bordered_box)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn marketplace_section<'a>(
|
||||
m: &MarketView,
|
||||
externals: &[ExternalPlugin],
|
||||
) -> Element<'a, Message> {
|
||||
let mut col = column![text("Available plugins").size(13).style(primary_style)].spacing(6);
|
||||
let mut visible = 0usize;
|
||||
if let Some(notice) = registry_notice(m) {
|
||||
col = col.push(notice);
|
||||
}
|
||||
|
||||
// Curated registry entries (from the OpenCADStudio repo).
|
||||
for e in m.registry {
|
||||
|
|
@ -482,7 +592,10 @@ fn marketplace_section<'a>(
|
|||
));
|
||||
}
|
||||
|
||||
if visible == 0 {
|
||||
if visible == 0
|
||||
&& m.registry_error.is_none()
|
||||
&& !(m.registry_loading && m.registry.is_empty())
|
||||
{
|
||||
let message = if m.search.trim().is_empty() {
|
||||
"No additional plugins are available."
|
||||
} else {
|
||||
|
|
@ -731,7 +844,7 @@ pub fn view_window<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{newest_update, repository_display_name};
|
||||
use super::{newest_update, registry_error_message, repository_display_name};
|
||||
|
||||
#[test]
|
||||
fn newest_update_uses_semver_not_release_order() {
|
||||
|
|
@ -751,4 +864,14 @@ mod tests {
|
|||
"OpenCAD Storm Sewer Plugin",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn certificate_errors_get_user_friendly_copy() {
|
||||
let (title, message) =
|
||||
registry_error_message("io: invalid peer certificate: UnknownIssuer");
|
||||
|
||||
assert_eq!(title, "Unable to verify the server certificate");
|
||||
assert!(message.contains("system certificate"));
|
||||
assert!(!message.contains("UnknownIssuer"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ pub fn load_cached() -> Vec<VideoEntry> {
|
|||
/// keeps whatever [`load_cached`] provided.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn fetch_playlist() -> Result<Vec<VideoEntry>, String> {
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.timeout_global(Some(std::time::Duration::from_secs(15)))
|
||||
.build()
|
||||
.into();
|
||||
let agent = crate::network::agent(std::time::Duration::from_secs(15));
|
||||
let page = agent
|
||||
.get(PLAYLIST_URL)
|
||||
.header("User-Agent", "Mozilla/5.0")
|
||||
|
|
|
|||
Loading…
Reference in a new issue