Skip to main content

tauri_plugin_updater/
config.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use std::{ffi::OsString, fmt::Display};
6
7use serde::{Deserialize, Deserializer};
8use url::Url;
9
10/// Install modes for the Windows update.
11#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
12#[serde(rename_all = "camelCase")]
13#[derive(Default)]
14pub enum WindowsUpdateInstallMode {
15    /// Specifies there's a basic UI during the installation process, including a final dialog box at the end.
16    BasicUi,
17    /// The quiet mode means there's no user interaction required.
18    /// Requires admin privileges if the installer does.
19    Quiet,
20    /// Specifies unattended mode, which means the installation only shows a progress bar.
21    #[default]
22    Passive,
23}
24
25impl WindowsUpdateInstallMode {
26    /// Returns the associated `msiexec.exe` arguments.
27    pub fn msiexec_args(&self) -> &'static [&'static str] {
28        match self {
29            Self::BasicUi => &["/qb+"],
30            Self::Quiet => &["/quiet"],
31            Self::Passive => &["/passive"],
32        }
33    }
34
35    #[cfg(windows)]
36    pub(crate) fn msi_restart_after_install_args(&self) -> &'static [&'static str] {
37        &["AUTOLAUNCHAPP=True"]
38    }
39
40    /// Returns the associated nsis arguments.
41    pub fn nsis_args(&self) -> &'static [&'static str] {
42        // `/P`: Passive
43        // `/S`: Silent
44        // `/R`: Restart
45        match self {
46            Self::Passive => &["/P"],
47            Self::Quiet => &["/S"],
48            _ => &[],
49        }
50    }
51
52    #[cfg(windows)]
53    pub(crate) fn nsis_restart_after_install_args(&self) -> &'static [&'static str] {
54        match self {
55            Self::BasicUi => &[],
56            _ => &["/R"],
57        }
58    }
59}
60
61impl Display for WindowsUpdateInstallMode {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(
64            f,
65            "{}",
66            match self {
67                Self::BasicUi => "basicUi",
68                Self::Quiet => "quiet",
69                Self::Passive => "passive",
70            }
71        )
72    }
73}
74
75#[derive(Debug, Clone, Deserialize, Default)]
76#[serde(rename_all = "camelCase")]
77pub struct WindowsConfig {
78    /// Additional arguments given to the NSIS or WiX installer.
79    ///
80    /// Note: this applies to both WiX and NSIS installers
81    #[serde(
82        default,
83        alias = "installer-args",
84        deserialize_with = "deserialize_os_string"
85    )]
86    pub installer_args: Vec<OsString>,
87    /// Updating mode, defaults to `passive` mode.
88    ///
89    /// See [`WindowsUpdateInstallMode`] for more info.
90    #[serde(default, alias = "install-mode")]
91    pub install_mode: WindowsUpdateInstallMode,
92}
93
94fn deserialize_os_string<'de, D>(deserializer: D) -> Result<Vec<OsString>, D::Error>
95where
96    D: Deserializer<'de>,
97{
98    Ok(Vec::<String>::deserialize(deserializer)?
99        .into_iter()
100        .map(OsString::from)
101        .collect::<Vec<_>>())
102}
103
104/// Updater configuration.
105#[derive(Debug, Clone, Default)]
106pub struct Config {
107    /// Dangerously allow using insecure transport protocols for update endpoints.
108    pub dangerous_insecure_transport_protocol: bool,
109    /// Dangerously accept invalid TLS certificates for update requests.
110    pub dangerous_accept_invalid_certs: bool,
111    /// Dangerously accept invalid hostnames for TLS certificates for update requests.
112    pub dangerous_accept_invalid_hostnames: bool,
113    /// Updater endpoints.
114    pub endpoints: Vec<Url>,
115    /// Signature public key.
116    pub pubkey: String,
117    /// Require the update signature to carry the version it was signed for, and reject the
118    /// update when that version differs from the one announced by the update endpoint.
119    ///
120    /// The endpoint response is fetched over TLS but is not itself signed, and the signature
121    /// only covers the downloaded artifact. Without this flag, anyone able to serve a crafted
122    /// response can pair an inflated `version` field with the `url` and `signature` of an
123    /// older release and force a downgrade to a genuine but outdated build, since that older
124    /// artifact carries a valid signature.
125    ///
126    /// The signed version is read from the signature's trusted comment, which is covered by
127    /// the signature. Releases signed before the Tauri CLI started recording it carry no
128    /// version, so enabling this rejects them. Re-sign and re-publish every release your users
129    /// can still update from before turning this on.
130    ///
131    /// This is checked independently of the version comparison: it constrains which artifact a
132    /// given version number may resolve to, not whether that version is newer.
133    ///
134    /// The default value of this flag is `false`.
135    pub require_signed_version: bool,
136    /// Allow the updater to install a release whose version is not newer than the
137    /// currently running one, changing the version check from "must be newer" to
138    /// "must be different".
139    ///
140    /// Note that the updater only verifies the signature of the downloaded artifact,
141    /// not the version advertised by the update endpoint, so enabling this removes the
142    /// only guard against installing a previously released (and validly signed) version.
143    ///
144    /// Ignored when the application sets a custom
145    /// [`Builder::default_version_comparator`](crate::Builder::default_version_comparator).
146    ///
147    /// The default value of this flag is `false`.
148    pub allow_downgrades: bool,
149    /// The Windows configuration for the updater.
150    pub windows: Option<WindowsConfig>,
151}
152
153impl<'de> Deserialize<'de> for Config {
154    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155    where
156        D: Deserializer<'de>,
157    {
158        #[derive(Deserialize)]
159        #[serde(rename_all = "camelCase")]
160        pub struct Config {
161            #[serde(default, alias = "dangerous-insecure-transport-protocol")]
162            pub dangerous_insecure_transport_protocol: bool,
163            #[serde(default, alias = "dangerous-accept-invalid-certs")]
164            pub dangerous_accept_invalid_certs: bool,
165            #[serde(default, alias = "dangerous-accept-invalid-hostnames")]
166            pub dangerous_accept_invalid_hostnames: bool,
167            #[serde(default)]
168            pub endpoints: Vec<Url>,
169            pub pubkey: String,
170            #[serde(default, alias = "require-signed-version")]
171            pub require_signed_version: bool,
172            #[serde(default, alias = "allow-downgrades")]
173            pub allow_downgrades: bool,
174            pub windows: Option<WindowsConfig>,
175        }
176
177        let config = Config::deserialize(deserializer)?;
178
179        validate_endpoints(
180            &config.endpoints,
181            config.dangerous_insecure_transport_protocol,
182        )
183        .map_err(serde::de::Error::custom)?;
184
185        Ok(Self {
186            dangerous_insecure_transport_protocol: config.dangerous_insecure_transport_protocol,
187            dangerous_accept_invalid_certs: config.dangerous_accept_invalid_certs,
188            dangerous_accept_invalid_hostnames: config.dangerous_accept_invalid_hostnames,
189            endpoints: config.endpoints,
190            pubkey: config.pubkey,
191            require_signed_version: config.require_signed_version,
192            allow_downgrades: config.allow_downgrades,
193            windows: config.windows,
194        })
195    }
196}
197
198pub(crate) fn validate_endpoints(
199    endpoints: &[Url],
200    dangerous_insecure_transport_protocol: bool,
201) -> crate::Result<()> {
202    if !dangerous_insecure_transport_protocol {
203        for url in endpoints {
204            if url.scheme() != "https" {
205                #[cfg(debug_assertions)]
206                {
207                    eprintln!("[\x1b[33mWARNING\x1b[0m] The updater endpoint \"{url}\" doesn't use `https` protocol. This is allowed in development but will fail in release builds.");
208                    eprintln!("[\x1b[33mWARNING\x1b[0m] if this is a desired behavior, you can enable `dangerousInsecureTransportProtocol` in the plugin configuration");
209                }
210                #[cfg(not(debug_assertions))]
211                return Err(crate::Error::InsecureTransportProtocol);
212            }
213        }
214    }
215
216    Ok(())
217}