Skip to content

Commit

Permalink
feat(updater): add method to set request headers closes #3896 (#3931)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog authored Apr 22, 2022
1 parent 38e330f commit 81705bb
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changes/http-api-header-method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Added `tauri::api::http::HttpRequestBuilder#header` method.
5 changes: 5 additions & 0 deletions .changes/http-api-header-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

**Breaking change:** The `tauri::api::http::HttpRequestBuilder#headers` method now takes `header::HeaderMap` instead of a `HashMap`.
5 changes: 5 additions & 0 deletions .changes/http-api-response-headers-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

**Breaking change:** The `tauri::api::http::Response#headers` method now returns `&header::HeaderMap` instead of `&HashMap`.
5 changes: 5 additions & 0 deletions .changes/updater-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Allow setting app updater request headers via `AppHandle::updater().header()`.
3 changes: 3 additions & 0 deletions core/tauri/src/api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ pub enum Error {
/// Unknown program name.
#[error("unknown program name: {0}")]
UnknownProgramName(String),
/// HTTP error.
#[error(transparent)]
Http(#[from] http::Error),
}

#[cfg(feature = "cli")]
Expand Down
85 changes: 66 additions & 19 deletions core/tauri/src/api/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@

//! Types and functions related to HTTP request.

use http::{header::HeaderName, Method};
pub use http::{HeaderMap, StatusCode};
use serde::{Deserialize, Serialize};
use http::Method;
pub use http::StatusCode;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr};
use url::Url;

use std::{collections::HashMap, time::Duration};

#[cfg(feature = "reqwest-client")]
pub use reqwest::header;

#[cfg(not(feature = "reqwest-client"))]
pub use attohttpc::header;

use header::{HeaderName, HeaderValue};

/// The builder of [`Client`].
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -107,11 +115,8 @@ impl Client {
}

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.as_bytes(),
);
for (name, value) in headers.0.iter() {
request_builder = request_builder.header(name, value);
}
}

Expand Down Expand Up @@ -183,16 +188,12 @@ impl Client {
};
}

let mut http_request = request_builder.build()?;
if let Some(headers) = request.headers {
for (header, value) in headers.iter() {
http_request.headers_mut().insert(
HeaderName::from_bytes(header.as_bytes())?,
http::header::HeaderValue::from_bytes(value.as_bytes())?,
);
}
request_builder = request_builder.headers(headers.0);
}

let http_request = request_builder.build()?;

let response = self.0.execute(http_request).await?;

Ok(Response(
Expand Down Expand Up @@ -252,6 +253,34 @@ pub enum Body {
Bytes(Vec<u8>),
}

/// A set of HTTP headers.
#[derive(Debug, Default)]
pub struct HeaderMap(header::HeaderMap);

impl<'de> Deserialize<'de> for HeaderMap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let map = HashMap::<String, String>::deserialize(deserializer)?;
let mut headers = header::HeaderMap::default();
for (key, value) in map {
if let (Ok(key), Ok(value)) = (
header::HeaderName::from_bytes(key.as_bytes()),
header::HeaderValue::from_str(&value),
) {
headers.insert(key, value);
} else {
return Err(serde::de::Error::custom(format!(
"invalid header `{}` `{}`",
key, value
)));
}
}
Ok(Self(headers))
}
}

/// The builder for a HTTP request.
///
/// # Examples
Expand Down Expand Up @@ -281,7 +310,7 @@ pub struct HttpRequestBuilder {
/// The request query params
pub query: Option<HashMap<String, String>>,
/// The request headers
pub headers: Option<HashMap<String, String>>,
pub headers: Option<HeaderMap>,
/// The request body
pub body: Option<Body>,
/// Timeout for the whole request
Expand Down Expand Up @@ -311,10 +340,28 @@ impl HttpRequestBuilder {
self
}

/// Adds a header.
pub fn header<K, V>(mut self, key: K, value: V) -> crate::api::Result<Self>
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let key: Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);
let value: Result<HeaderValue, http::Error> = value.try_into().map_err(Into::into);
self
.headers
.get_or_insert_with(Default::default)
.0
.insert(key?, value?);
Ok(self)
}

/// Sets the request headers.
#[must_use]
pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
self.headers = Some(headers);
pub fn headers(mut self, headers: header::HeaderMap) -> Self {
self.headers.replace(HeaderMap(headers));
self
}

Expand Down Expand Up @@ -356,7 +403,7 @@ impl Response {
}

/// Get the headers of this Response.
pub fn headers(&self) -> &HeaderMap {
pub fn headers(&self) -> &header::HeaderMap {
self.1.headers()
}

Expand Down
51 changes: 41 additions & 10 deletions core/tauri/src/updater/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ use crate::{
AppHandle, Manager, Runtime,
};
use base64::decode;
use http::StatusCode;
use http::{
header::{HeaderName, HeaderValue},
HeaderMap, StatusCode,
};
use minisign_verify::{PublicKey, Signature};
use tauri_utils::{platform::current_exe, Env};

#[cfg(feature = "updater")]
use std::io::Seek;
use std::{
collections::HashMap,
env, fmt,
io::{Cursor, Read},
path::{Path, PathBuf},
Expand Down Expand Up @@ -212,6 +214,7 @@ pub struct UpdateBuilder<R: Runtime> {
pub executable_path: Option<PathBuf>,
should_install: Option<Box<dyn FnOnce(&str, &str) -> bool + Send>>,
timeout: Option<Duration>,
headers: HeaderMap,
}

impl<R: Runtime> fmt::Debug for UpdateBuilder<R> {
Expand All @@ -223,6 +226,7 @@ impl<R: Runtime> fmt::Debug for UpdateBuilder<R> {
.field("target", &self.target)
.field("executable_path", &self.executable_path)
.field("timeout", &self.timeout)
.field("headers", &self.headers)
.finish()
}
}
Expand All @@ -238,6 +242,7 @@ impl<R: Runtime> UpdateBuilder<R> {
current_version: env!("CARGO_PKG_VERSION").into(),
should_install: None,
timeout: None,
headers: Default::default(),
}
}

Expand Down Expand Up @@ -295,6 +300,20 @@ impl<R: Runtime> UpdateBuilder<R> {
self
}

/// Add a `Header` to the request.
pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let key: std::result::Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);
let value: std::result::Result<HeaderValue, http::Error> = value.try_into().map_err(Into::into);
self.headers.insert(key?, value?);
Ok(self)
}

pub async fn build(mut self) -> Result<Update<R>> {
let mut remote_release: Option<RemoteRelease> = None;

Expand Down Expand Up @@ -336,6 +355,10 @@ impl<R: Runtime> UpdateBuilder<R> {
}
}

// we want JSON only
let mut headers = self.headers;
headers.insert("Accept", HeaderValue::from_str("application/json").unwrap());

// Allow fallback if more than 1 urls is provided
let mut last_error: Option<Error> = None;
for url in &self.urls {
Expand All @@ -351,11 +374,7 @@ impl<R: Runtime> UpdateBuilder<R> {
.replace("{{target}}", &target)
.replace("{{arch}}", arch);

// we want JSON only
let mut headers = HashMap::new();
headers.insert("Accept".into(), "application/json".into());

let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers);
let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers.clone());
if let Some(timeout) = self.timeout {
request = request.timeout(timeout);
}
Expand Down Expand Up @@ -408,6 +427,8 @@ impl<R: Runtime> UpdateBuilder<R> {
version::is_greater(&self.current_version, &final_release.version).unwrap_or(false)
};

headers.remove("Accept");

// create our new updater
Ok(Update {
app: self.app,
Expand All @@ -423,6 +444,7 @@ impl<R: Runtime> UpdateBuilder<R> {
#[cfg(target_os = "windows")]
with_elevated_task: final_release.with_elevated_task,
timeout: self.timeout,
headers,
})
}
}
Expand Down Expand Up @@ -460,6 +482,8 @@ pub struct Update<R: Runtime> {
with_elevated_task: bool,
/// Request timeout
timeout: Option<Duration>,
/// Request headers
headers: HeaderMap,
}

impl<R: Runtime> Clone for Update<R> {
Expand All @@ -478,6 +502,7 @@ impl<R: Runtime> Clone for Update<R> {
#[cfg(target_os = "windows")]
with_elevated_task: self.with_elevated_task,
timeout: self.timeout,
headers: self.headers.clone(),
}
}
}
Expand All @@ -502,9 +527,15 @@ impl<R: Runtime> Update<R> {
}

// set our headers
let mut headers = HashMap::new();
headers.insert("Accept".into(), "application/octet-stream".into());
headers.insert("User-Agent".into(), "tauri/updater".into());
let mut headers = self.headers.clone();
headers.insert(
"Accept",
HeaderValue::from_str("application/octet-stream").unwrap(),
);
headers.insert(
"User-Agent",
HeaderValue::from_str("tauri/updater").unwrap(),
);

let client = ClientBuilder::new().build()?;
// Create our request
Expand Down
3 changes: 3 additions & 0 deletions core/tauri/src/updater/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ pub enum Error {
/// The updater responded with an invalid signature type.
#[error("the updater response field `{0}` type is invalid, expected {1} but found {2}")]
InvalidResponseType(&'static str, &'static str, serde_json::Value),
/// HTTP error.
#[error(transparent)]
Http(#[from] http::Error),
}

pub type Result<T = ()> = std::result::Result<T, Error>;
14 changes: 14 additions & 0 deletions core/tauri/src/updater/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ mod error;

use std::time::Duration;

use http::header::{HeaderName, HeaderValue};

pub use self::error::Error;
/// Alias for [`std::result::Result`] using our own [`Error`].
pub type Result<T> = std::result::Result<T, Error>;
Expand Down Expand Up @@ -557,6 +559,18 @@ impl<R: Runtime> UpdateBuilder<R> {
self
}

/// Add a `Header` to the request.
pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.inner = self.inner.header(key, value)?;
Ok(self)
}

/// Check if an update is available.
///
/// # Examples
Expand Down

0 comments on commit 81705bb

Please sign in to comment.