mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-11-28 12:27:16 +03:00
refactor(core): use attohttpc
by default (#1861)
This commit is contained in:
parent
f237435093
commit
17c7c4396f
5
.changes/attohttpc-default-client.md
Normal file
5
.changes/attohttpc-default-client.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Use `attohttpc` on the HTTP API by default for bundle size optimization. `reqwest` is implemented behind the `reqwest-client` feature flag.
|
5
.changes/core-features.md
Normal file
5
.changes/core-features.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"cli.rs": patch
|
||||
---
|
||||
|
||||
Properly keep all `tauri` features that are not managed by the CLI.
|
@ -27,6 +27,9 @@ targets = [
|
||||
"x86_64-apple-darwin"
|
||||
]
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["attohttpc"] # we ignore attohttpc because we can't remove it based on `not(feature = "reqwest-client")`
|
||||
|
||||
[dependencies]
|
||||
serde_json = { version = "1.0", features = [ "raw_value" ] }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
@ -41,7 +44,6 @@ tauri-macros = { version = "1.0.0-beta.1", path = "../tauri-macros" }
|
||||
tauri-utils = { version = "1.0.0-beta.0", path = "../tauri-utils" }
|
||||
tauri-runtime-wry = { version = "0.1.1", path = "../tauri-runtime-wry", optional = true }
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11", features = [ "json", "multipart" ] }
|
||||
tempfile = "3"
|
||||
semver = "0.11"
|
||||
serde_repr = "0.1"
|
||||
@ -53,7 +55,6 @@ tar = "0.4"
|
||||
flate2 = "1.0"
|
||||
rfd = "0.3.0"
|
||||
tinyfiledialogs = "3.3"
|
||||
bytes = { version = "1", features = [ "serde" ] }
|
||||
http = "0.2"
|
||||
clap = { version = "=3.0.0-beta.2", optional = true }
|
||||
notify-rust = { version = "4.5.0", optional = true }
|
||||
@ -65,6 +66,11 @@ minisign-verify = "0.1.8"
|
||||
state = "0.4"
|
||||
bincode = "1.3"
|
||||
|
||||
# HTTP
|
||||
reqwest = { version = "0.11", features = [ "json", "multipart" ], optional = true }
|
||||
bytes = { version = "1", features = [ "serde" ], optional = true }
|
||||
attohttpc = { version = "0.17", features = [ "json", "form" ] }
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.1.1"
|
||||
|
||||
@ -85,9 +91,10 @@ wry = [ "tauri-runtime-wry" ]
|
||||
cli = [ "clap" ]
|
||||
custom-protocol = [ "tauri-macros/custom-protocol" ]
|
||||
api-all = [ "notification-all", "global-shortcut-all", "updater" ]
|
||||
updater = [ "reqwest/default-tls" ]
|
||||
updater = [ ]
|
||||
menu = [ "tauri-runtime/menu", "tauri-runtime-wry/menu" ]
|
||||
system-tray = [ "tauri-runtime/system-tray", "tauri-runtime-wry/system-tray" ]
|
||||
reqwest-client = [ "reqwest", "bytes" ]
|
||||
fs-all = [ ]
|
||||
fs-read-text-file = [ ]
|
||||
fs-read-binary-file = [ ]
|
||||
|
@ -25,6 +25,11 @@ pub enum Error {
|
||||
#[error("user cancelled the dialog")]
|
||||
DialogCancelled,
|
||||
/// The network error.
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
#[error("Network Error: {0}")]
|
||||
Network(#[from] attohttpc::Error),
|
||||
/// The network error.
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
#[error("Network Error: {0}")]
|
||||
Network(#[from] reqwest::Error),
|
||||
/// HTTP method error.
|
||||
@ -32,10 +37,10 @@ pub enum Error {
|
||||
HttpMethod(#[from] http::method::InvalidMethod),
|
||||
/// Invalid HTTO header.
|
||||
#[error("{0}")]
|
||||
HttpHeader(#[from] reqwest::header::InvalidHeaderName),
|
||||
HttpHeader(#[from] http::header::InvalidHeaderName),
|
||||
/// Failed to serialize header value as string.
|
||||
#[error("failed to convert response header value to string")]
|
||||
HttpHeaderToString(#[from] reqwest::header::ToStrError),
|
||||
HttpHeaderToString(#[from] http::header::ToStrError),
|
||||
/// HTTP form to must be an object.
|
||||
#[error("http form must be an object")]
|
||||
InvalidHttpForm,
|
||||
|
@ -2,8 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use bytes::Bytes;
|
||||
use reqwest::{header::HeaderName, redirect::Policy, Method};
|
||||
use http::{header::HeaderName, Method};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
@ -11,7 +10,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||
|
||||
/// Client builder.
|
||||
#[derive(Default, Deserialize)]
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClientBuilder {
|
||||
/// Max number of redirections to follow
|
||||
@ -38,12 +37,19 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the ClientOptions.
|
||||
/// Builds the Client.
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
pub fn build(self) -> crate::api::Result<Client> {
|
||||
Ok(Client(self))
|
||||
}
|
||||
|
||||
/// Builds the Client.
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
pub fn build(self) -> crate::api::Result<Client> {
|
||||
let mut client_builder = reqwest::Client::builder();
|
||||
|
||||
if let Some(max_redirections) = self.max_redirections {
|
||||
client_builder = client_builder.redirect(Policy::limited(max_redirections))
|
||||
client_builder = client_builder.redirect(reqwest::redirect::Policy::limited(max_redirections))
|
||||
}
|
||||
|
||||
if let Some(connect_timeout) = self.connect_timeout {
|
||||
@ -56,16 +62,80 @@ impl ClientBuilder {
|
||||
}
|
||||
|
||||
/// The HTTP client.
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
#[derive(Clone)]
|
||||
pub struct Client(reqwest::Client);
|
||||
|
||||
/// The HTTP client.
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
#[derive(Clone)]
|
||||
pub struct Client(ClientBuilder);
|
||||
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
impl Client {
|
||||
/// Executes an HTTP request
|
||||
///
|
||||
/// The response will be transformed to String,
|
||||
/// If reading the response as binary, the byte array will be serialized using serde_json
|
||||
/// If reading the response as binary, the byte array will be serialized using serde_json.
|
||||
pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
|
||||
let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
|
||||
|
||||
let mut request_builder = attohttpc::RequestBuilder::try_new(method, &request.url)?;
|
||||
|
||||
if let Some(query) = request.query {
|
||||
request_builder = request_builder.params(&query);
|
||||
}
|
||||
|
||||
if let Some(headers) = request.headers {
|
||||
for (header, header_value) in headers.iter() {
|
||||
request_builder =
|
||||
request_builder.header(HeaderName::from_bytes(header.as_bytes())?, header_value);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(timeout) = request.timeout {
|
||||
request_builder = request_builder.timeout(Duration::from_secs(timeout));
|
||||
}
|
||||
|
||||
let response = if let Some(body) = request.body {
|
||||
match body {
|
||||
Body::Bytes(data) => request_builder.body(attohttpc::body::Bytes(data)).send()?,
|
||||
Body::Text(text) => request_builder.body(attohttpc::body::Bytes(text)).send()?,
|
||||
Body::Json(json) => request_builder.json(&json)?.send()?,
|
||||
Body::Form(form_body) => {
|
||||
let mut form = Vec::new();
|
||||
for (name, part) in form_body.0 {
|
||||
match part {
|
||||
FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)),
|
||||
FormPart::File(file_path) => form.push((name, serde_json::to_string(&file_path)?)),
|
||||
FormPart::Text(text) => form.push((name, text)),
|
||||
}
|
||||
}
|
||||
request_builder.form(&form)?.send()?
|
||||
}
|
||||
}
|
||||
} else {
|
||||
request_builder.send()?
|
||||
};
|
||||
|
||||
let response = response.error_for_status()?;
|
||||
Ok(Response(
|
||||
request.response_type.unwrap_or(ResponseType::Json),
|
||||
response,
|
||||
request.url,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
impl Client {
|
||||
/// Executes an HTTP request
|
||||
///
|
||||
/// The response will be transformed to String,
|
||||
/// If reading the response as binary, the byte array will be serialized using serde_json.
|
||||
pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
|
||||
let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
|
||||
|
||||
let mut request_builder = self.0.request(method, &request.url);
|
||||
|
||||
if let Some(query) = request.query {
|
||||
@ -85,8 +155,18 @@ impl Client {
|
||||
|
||||
let response = if let Some(body) = request.body {
|
||||
match body {
|
||||
Body::Bytes(data) => request_builder.body(Bytes::from(data)).send().await?,
|
||||
Body::Text(text) => request_builder.body(Bytes::from(text)).send().await?,
|
||||
Body::Bytes(data) => {
|
||||
request_builder
|
||||
.body(bytes::Bytes::from(data))
|
||||
.send()
|
||||
.await?
|
||||
}
|
||||
Body::Text(text) => {
|
||||
request_builder
|
||||
.body(bytes::Bytes::from(text))
|
||||
.send()
|
||||
.await?
|
||||
}
|
||||
Body::Json(json) => request_builder.json(&json).send().await?,
|
||||
Body::Form(form_body) => {
|
||||
let mut form = Vec::new();
|
||||
@ -249,24 +329,50 @@ impl HttpRequestBuilder {
|
||||
}
|
||||
|
||||
/// The HTTP response.
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
pub struct Response(ResponseType, reqwest::Response);
|
||||
/// The HTTP response.
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
pub struct Response(ResponseType, attohttpc::Response, String);
|
||||
|
||||
impl Response {
|
||||
/// Reads the response as raw bytes.
|
||||
pub async fn bytes(self) -> crate::api::Result<RawResponse> {
|
||||
let status = self.1.status().as_u16();
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
let data = self.1.bytes().await?.to_vec();
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
let data = self.1.bytes()?;
|
||||
Ok(RawResponse { status, data })
|
||||
}
|
||||
|
||||
/// Reads the response and returns its info.
|
||||
pub async fn read(self) -> crate::api::Result<ResponseData> {
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
let url = self.1.url().to_string();
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
let url = self.2;
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
for (name, value) in self.1.headers() {
|
||||
headers.insert(name.as_str().to_string(), value.to_str()?.to_string());
|
||||
}
|
||||
let status = self.1.status().as_u16();
|
||||
|
||||
#[cfg(feature = "reqwest-client")]
|
||||
let data = match self.0 {
|
||||
ResponseType::Json => self.1.json().await?,
|
||||
ResponseType::Text => Value::String(self.1.text().await?),
|
||||
ResponseType::Binary => Value::String(serde_json::to_string(&self.1.bytes().await?)?),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "reqwest-client"))]
|
||||
let data = match self.0 {
|
||||
ResponseType::Json => self.1.json()?,
|
||||
ResponseType::Text => Value::String(self.1.text()?),
|
||||
ResponseType::Binary => Value::String(serde_json::to_string(&self.1.bytes()?)?),
|
||||
};
|
||||
|
||||
Ok(ResponseData {
|
||||
url,
|
||||
status,
|
||||
@ -276,12 +382,26 @@ impl Response {
|
||||
}
|
||||
}
|
||||
|
||||
/// A response with raw bytes.
|
||||
#[non_exhaustive]
|
||||
pub struct RawResponse {
|
||||
/// Response status code.
|
||||
pub status: u16,
|
||||
/// Response bytes.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The response type.
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub struct ResponseData {
|
||||
url: String,
|
||||
status: u16,
|
||||
headers: HashMap<String, String>,
|
||||
data: Value,
|
||||
/// Response URL. Useful if it followed redirects.
|
||||
pub url: String,
|
||||
/// Response status code.
|
||||
pub status: u16,
|
||||
/// Response headers.
|
||||
pub headers: HashMap<String, String>,
|
||||
/// Response data.
|
||||
pub data: Value,
|
||||
}
|
||||
|
@ -5,16 +5,17 @@
|
||||
use super::error::{Error, Result};
|
||||
use crate::api::{file::Extract, version};
|
||||
use base64::decode;
|
||||
use http::StatusCode;
|
||||
use minisign_verify::{PublicKey, Signature};
|
||||
use reqwest::{self, header, StatusCode};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
ffi::OsStr,
|
||||
fs::{read_dir, remove_file, File, OpenOptions},
|
||||
io::{prelude::*, BufReader, Read},
|
||||
path::{Path, PathBuf},
|
||||
str::from_utf8,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@ -23,6 +24,8 @@ use std::process::Command;
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::api::file::Move;
|
||||
|
||||
use crate::api::http::{ClientBuilder, HttpRequestBuilder};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::process::exit;
|
||||
|
||||
@ -271,31 +274,33 @@ impl<'a> UpdateBuilder<'a> {
|
||||
);
|
||||
|
||||
// we want JSON only
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(header::ACCEPT, "application/json".parse().unwrap());
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Accept".into(), "application/json".into());
|
||||
|
||||
let resp = reqwest::Client::new()
|
||||
.get(&fixed_link)
|
||||
.headers(headers)
|
||||
// wait 20sec for the firewall
|
||||
.timeout(Duration::from_secs(20))
|
||||
.send()
|
||||
let resp = ClientBuilder::new()
|
||||
.build()?
|
||||
.send(
|
||||
HttpRequestBuilder::new("GET", &fixed_link)
|
||||
.headers(headers)
|
||||
// wait 20sec for the firewall
|
||||
.timeout(20),
|
||||
)
|
||||
.await;
|
||||
|
||||
// If we got a success, we stop the loop
|
||||
// and we set our remote_release variable
|
||||
if let Ok(ref res) = resp {
|
||||
if let Ok(res) = resp {
|
||||
let res = res.read().await?;
|
||||
// got status code 2XX
|
||||
if res.status().is_success() {
|
||||
if StatusCode::from_u16(res.status).unwrap().is_success() {
|
||||
// if we got 204
|
||||
if StatusCode::NO_CONTENT == res.status() {
|
||||
if StatusCode::NO_CONTENT.as_u16() == res.status {
|
||||
// return with `UpToDate` error
|
||||
// we should catch on the client
|
||||
return Err(Error::UpToDate);
|
||||
};
|
||||
let json = resp?.json::<serde_json::Value>().await?;
|
||||
// Convert the remote result to our local struct
|
||||
let built_release = RemoteRelease::from_release(&json, &target);
|
||||
let built_release = RemoteRelease::from_release(&res.data, &target);
|
||||
// make sure all went well and the remote data is compatible
|
||||
// with what we need locally
|
||||
match built_release {
|
||||
@ -411,35 +416,32 @@ impl Update {
|
||||
let mut tmp_archive = File::create(&tmp_archive_path)?;
|
||||
|
||||
// set our headers
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(header::ACCEPT, "application/octet-stream".parse().unwrap());
|
||||
|
||||
// make sure we have a valid agent
|
||||
if !headers.contains_key(header::USER_AGENT) {
|
||||
headers.insert(
|
||||
header::USER_AGENT,
|
||||
"tauri/updater".parse().expect("invalid user-agent"),
|
||||
);
|
||||
}
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Accept".into(), "application/octet-stream".into());
|
||||
headers.insert("User-Agent".into(), "tauri/updater".into());
|
||||
|
||||
// Create our request
|
||||
let resp = reqwest::Client::new()
|
||||
.get(&url)
|
||||
// wait 20sec for the firewall
|
||||
.timeout(Duration::from_secs(20))
|
||||
.headers(headers)
|
||||
.send()
|
||||
let resp = ClientBuilder::new()
|
||||
.build()?
|
||||
.send(
|
||||
HttpRequestBuilder::new("GET", &url)
|
||||
.headers(headers)
|
||||
// wait 20sec for the firewall
|
||||
.timeout(20),
|
||||
)
|
||||
.await?
|
||||
.bytes()
|
||||
.await?;
|
||||
|
||||
// make sure it's success
|
||||
if !resp.status().is_success() {
|
||||
if !StatusCode::from_u16(resp.status).unwrap().is_success() {
|
||||
return Err(Error::Network(format!(
|
||||
"Download request failed with status: {}",
|
||||
resp.status()
|
||||
resp.status
|
||||
)));
|
||||
}
|
||||
|
||||
tmp_archive.write_all(&resp.bytes().await?)?;
|
||||
tmp_archive.write_all(&resp.data)?;
|
||||
|
||||
// Validate signature ONLY if pubkey is available in tauri.conf.json
|
||||
if let Some(pub_key) = pub_key {
|
||||
|
@ -11,9 +11,6 @@ pub enum Error {
|
||||
/// IO Errors.
|
||||
#[error("`{0}`")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// Reqwest Errors.
|
||||
#[error("Request error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
/// Semver Errors.
|
||||
#[error("Unable to compare version: {0}")]
|
||||
Semver(#[from] semver::SemVerError),
|
||||
|
@ -11,7 +11,7 @@ tauri-build = { path = "../../../core/tauri-build" }
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "updater", "system-tray", "menu"] }
|
||||
tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "menu", "system-tray", "updater"] }
|
||||
|
||||
[features]
|
||||
default = [ "custom-protocol" ]
|
||||
|
@ -300,7 +300,7 @@ pub struct SecurityConfig {
|
||||
pub csp: Option<String>,
|
||||
}
|
||||
|
||||
trait Allowlist {
|
||||
pub trait Allowlist {
|
||||
fn to_features(&self) -> Vec<&str>;
|
||||
}
|
||||
|
||||
@ -314,31 +314,31 @@ macro_rules! check_feature {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct FsAllowlistConfig {
|
||||
pub struct FsAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
read_text_file: bool,
|
||||
pub read_text_file: bool,
|
||||
#[serde(default)]
|
||||
read_binary_file: bool,
|
||||
pub read_binary_file: bool,
|
||||
#[serde(default)]
|
||||
write_file: bool,
|
||||
pub write_file: bool,
|
||||
#[serde(default)]
|
||||
write_binary_file: bool,
|
||||
pub write_binary_file: bool,
|
||||
#[serde(default)]
|
||||
read_dir: bool,
|
||||
pub read_dir: bool,
|
||||
#[serde(default)]
|
||||
copy_file: bool,
|
||||
pub copy_file: bool,
|
||||
#[serde(default)]
|
||||
create_dir: bool,
|
||||
pub create_dir: bool,
|
||||
#[serde(default)]
|
||||
remove_dir: bool,
|
||||
pub remove_dir: bool,
|
||||
#[serde(default)]
|
||||
remove_file: bool,
|
||||
pub remove_file: bool,
|
||||
#[serde(default)]
|
||||
rename_file: bool,
|
||||
pub rename_file: bool,
|
||||
#[serde(default)]
|
||||
path: bool,
|
||||
pub path: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for FsAllowlistConfig {
|
||||
@ -365,11 +365,11 @@ impl Allowlist for FsAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct WindowAllowlistConfig {
|
||||
pub struct WindowAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
create: bool,
|
||||
pub create: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for WindowAllowlistConfig {
|
||||
@ -386,13 +386,13 @@ impl Allowlist for WindowAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct ShellAllowlistConfig {
|
||||
pub struct ShellAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
execute: bool,
|
||||
pub execute: bool,
|
||||
#[serde(default)]
|
||||
open: bool,
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for ShellAllowlistConfig {
|
||||
@ -410,13 +410,13 @@ impl Allowlist for ShellAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct DialogAllowlistConfig {
|
||||
pub struct DialogAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
open: bool,
|
||||
pub open: bool,
|
||||
#[serde(default)]
|
||||
save: bool,
|
||||
pub save: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for DialogAllowlistConfig {
|
||||
@ -434,11 +434,11 @@ impl Allowlist for DialogAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct HttpAllowlistConfig {
|
||||
pub struct HttpAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
request: bool,
|
||||
pub request: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for HttpAllowlistConfig {
|
||||
@ -455,9 +455,9 @@ impl Allowlist for HttpAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct NotificationAllowlistConfig {
|
||||
pub struct NotificationAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for NotificationAllowlistConfig {
|
||||
@ -472,9 +472,9 @@ impl Allowlist for NotificationAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct GlobalShortcutAllowlistConfig {
|
||||
pub struct GlobalShortcutAllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
impl Allowlist for GlobalShortcutAllowlistConfig {
|
||||
@ -489,23 +489,23 @@ impl Allowlist for GlobalShortcutAllowlistConfig {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct AllowlistConfig {
|
||||
pub struct AllowlistConfig {
|
||||
#[serde(default)]
|
||||
all: bool,
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
fs: FsAllowlistConfig,
|
||||
pub fs: FsAllowlistConfig,
|
||||
#[serde(default)]
|
||||
window: WindowAllowlistConfig,
|
||||
pub window: WindowAllowlistConfig,
|
||||
#[serde(default)]
|
||||
shell: ShellAllowlistConfig,
|
||||
pub shell: ShellAllowlistConfig,
|
||||
#[serde(default)]
|
||||
dialog: DialogAllowlistConfig,
|
||||
pub dialog: DialogAllowlistConfig,
|
||||
#[serde(default)]
|
||||
http: HttpAllowlistConfig,
|
||||
pub http: HttpAllowlistConfig,
|
||||
#[serde(default)]
|
||||
notification: NotificationAllowlistConfig,
|
||||
pub notification: NotificationAllowlistConfig,
|
||||
#[serde(default)]
|
||||
global_shortcut: GlobalShortcutAllowlistConfig,
|
||||
pub global_shortcut: GlobalShortcutAllowlistConfig,
|
||||
}
|
||||
|
||||
impl Allowlist for AllowlistConfig {
|
||||
|
@ -383,7 +383,7 @@ fn tauri_config_to_bundle_settings(
|
||||
// provides `libwebkit2gtk-4.0.so.37` and all `4.0` versions have the -37 package name
|
||||
depends.push("libwebkit2gtk-4.0-37".to_string());
|
||||
depends.push("libgtk-3-0".to_string());
|
||||
if manifest.features.contains(&"menu".into()) || system_tray_config.is_some() {
|
||||
if manifest.features.contains("menu") || system_tray_config.is_some() {
|
||||
depends.push("libgtksourceview-3.0-1".to_string());
|
||||
}
|
||||
}
|
||||
|
@ -101,3 +101,44 @@ pub fn reload(merge_config: Option<&str>) -> crate::Result<()> {
|
||||
get_internal(merge_config, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn all_allowlist_features() -> Vec<&'static str> {
|
||||
AllowlistConfig {
|
||||
all: true,
|
||||
fs: FsAllowlistConfig {
|
||||
all: true,
|
||||
read_text_file: true,
|
||||
read_binary_file: true,
|
||||
write_file: true,
|
||||
write_binary_file: true,
|
||||
read_dir: true,
|
||||
copy_file: true,
|
||||
create_dir: true,
|
||||
remove_dir: true,
|
||||
remove_file: true,
|
||||
rename_file: true,
|
||||
path: true,
|
||||
},
|
||||
window: WindowAllowlistConfig {
|
||||
all: true,
|
||||
create: true,
|
||||
},
|
||||
shell: ShellAllowlistConfig {
|
||||
all: true,
|
||||
execute: true,
|
||||
open: true,
|
||||
},
|
||||
dialog: DialogAllowlistConfig {
|
||||
all: true,
|
||||
open: true,
|
||||
save: true,
|
||||
},
|
||||
http: HttpAllowlistConfig {
|
||||
all: true,
|
||||
request: true,
|
||||
},
|
||||
notification: NotificationAllowlistConfig { all: true },
|
||||
global_shortcut: GlobalShortcutAllowlistConfig { all: true },
|
||||
}
|
||||
.to_features()
|
||||
}
|
||||
|
@ -2,19 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{app_paths::tauri_dir, config::ConfigHandle};
|
||||
use super::{
|
||||
app_paths::tauri_dir,
|
||||
config::{all_allowlist_features, ConfigHandle},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use toml_edit::{Array, Document, InlineTable, Item, Value};
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub struct Manifest {
|
||||
pub features: Vec<String>,
|
||||
pub features: HashSet<String>,
|
||||
}
|
||||
|
||||
fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
|
||||
@ -31,14 +35,14 @@ fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
fn features_to_vec(features: &Array) -> Vec<String> {
|
||||
let mut string_features = Vec::new();
|
||||
for feat in features.iter() {
|
||||
if let Value::String(feature) = feat {
|
||||
string_features.push(feature.value().to_string());
|
||||
}
|
||||
fn toml_array(features: &HashSet<String>) -> Array {
|
||||
let mut f = Array::default();
|
||||
let mut features: Vec<String> = features.iter().map(|f| f.to_string()).collect();
|
||||
features.sort();
|
||||
for feature in features {
|
||||
f.push(feature.as_str()).unwrap();
|
||||
}
|
||||
string_features
|
||||
f
|
||||
}
|
||||
|
||||
pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
|
||||
@ -56,32 +60,35 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
|
||||
let config = config_guard.as_ref().unwrap();
|
||||
|
||||
let allowlist_features = config.tauri.features();
|
||||
let mut features = Array::default();
|
||||
let mut features = HashSet::new();
|
||||
for feature in allowlist_features {
|
||||
features.push(feature).unwrap();
|
||||
features.insert(feature.to_string());
|
||||
}
|
||||
if config.tauri.cli.is_some() {
|
||||
features.push("cli").unwrap();
|
||||
features.insert("cli".to_string());
|
||||
}
|
||||
if config.tauri.updater.active {
|
||||
features.push("updater").unwrap();
|
||||
features.insert("updater".to_string());
|
||||
}
|
||||
if config.tauri.system_tray.is_some() {
|
||||
features.push("system-tray").unwrap();
|
||||
features.insert("system-tray".to_string());
|
||||
}
|
||||
|
||||
let mut cli_managed_features = all_allowlist_features();
|
||||
cli_managed_features.extend(vec!["cli", "updater", "system-tray"]);
|
||||
|
||||
if let Some(tauri) = tauri_entry.as_table_mut() {
|
||||
let manifest_features = tauri.entry("features");
|
||||
if let Item::Value(Value::Array(f)) = &manifest_features {
|
||||
for feat in f.iter() {
|
||||
if let Value::String(feature) = feat {
|
||||
if feature.value() == "menu" {
|
||||
features.push("menu").unwrap();
|
||||
if !cli_managed_features.contains(&feature.value().as_str()) {
|
||||
features.insert(feature.value().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*manifest_features = Item::Value(Value::Array(features.clone()));
|
||||
*manifest_features = Item::Value(Value::Array(toml_array(&features)));
|
||||
} else if let Some(tauri) = tauri_entry.as_value_mut() {
|
||||
match tauri {
|
||||
Value::InlineTable(table) => {
|
||||
@ -89,13 +96,13 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
|
||||
if let Value::Array(f) = &manifest_features {
|
||||
for feat in f.iter() {
|
||||
if let Value::String(feature) = feat {
|
||||
if feature.value() == "menu" {
|
||||
features.push("menu").unwrap();
|
||||
if !cli_managed_features.contains(&feature.value().as_str()) {
|
||||
features.insert(feature.value().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*manifest_features = Value::Array(features.clone());
|
||||
*manifest_features = Value::Array(toml_array(&features));
|
||||
}
|
||||
Value::String(version) => {
|
||||
let mut def = InlineTable::default();
|
||||
@ -103,7 +110,7 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
|
||||
"version",
|
||||
version.to_string().replace("\"", "").replace(" ", ""),
|
||||
);
|
||||
def.get_or_insert("features", Value::Array(features.clone()));
|
||||
def.get_or_insert("features", Value::Array(toml_array(&features)));
|
||||
*tauri = Value::InlineTable(def);
|
||||
}
|
||||
_ => {
|
||||
@ -113,9 +120,7 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Ok(Manifest {
|
||||
features: features_to_vec(&features),
|
||||
});
|
||||
return Ok(Manifest { features });
|
||||
}
|
||||
|
||||
let mut manifest_file =
|
||||
@ -132,9 +137,7 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
|
||||
)?;
|
||||
manifest_file.flush()?;
|
||||
|
||||
Ok(Manifest {
|
||||
features: features_to_vec(&features),
|
||||
})
|
||||
Ok(Manifest { features })
|
||||
}
|
||||
|
||||
pub fn get_workspace_members() -> crate::Result<Vec<String>> {
|
||||
|
Loading…
Reference in New Issue
Block a user