diff --git a/CHANGELOG.md b/CHANGELOG.md index 781fee77e..b2e78974b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +### Changes +- Add `get_http_blob` JSON-RPC API. + ## [1.112.7] - 2023-04-17 ### Fixes diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 3ed2605e9..de51d2b37 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -1610,6 +1610,17 @@ impl CommandApi { Ok(general_purpose::STANDARD_NO_PAD.encode(blob)) } + /// Makes an HTTP GET request and returns base64-encoded contents. + /// + /// `url` is the HTTP or HTTPS URL. + async fn get_http_blob(&self, account_id: u32, url: String) -> Result { + let ctx = self.get_context(account_id).await?; + let blob = deltachat::net::read_url_blob(&ctx, &url).await?; + + use base64::{engine::general_purpose, Engine as _}; + Ok(general_purpose::STANDARD_NO_PAD.encode(blob)) + } + /// Forward messages to another chat. /// /// All types of messages can be forwarded, diff --git a/src/configure.rs b/src/configure.rs index eb69ad25c..eb929440e 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -2,7 +2,6 @@ mod auto_mozilla; mod auto_outlook; -mod read_url; mod server_params; use anyhow::{bail, ensure, Context as _, Result}; diff --git a/src/configure/auto_mozilla.rs b/src/configure/auto_mozilla.rs index d53b5a8c5..b71055d81 100644 --- a/src/configure/auto_mozilla.rs +++ b/src/configure/auto_mozilla.rs @@ -6,10 +6,10 @@ use std::str::FromStr; use quick_xml::events::{BytesStart, Event}; -use super::read_url::read_url; use super::{Error, ServerParams}; use crate::context::Context; use crate::login_param::LoginParam; +use crate::net::read_url; use crate::provider::{Protocol, Socket}; #[derive(Debug)] diff --git a/src/configure/auto_outlook.rs b/src/configure/auto_outlook.rs index 8d42fd353..c1cfbe416 100644 --- a/src/configure/auto_outlook.rs +++ b/src/configure/auto_outlook.rs @@ -7,9 +7,9 @@ use std::io::BufRead; use quick_xml::events::Event; -use super::read_url::read_url; use super::{Error, ServerParams}; use crate::context::Context; +use crate::net::read_url; use crate::provider::{Protocol, Socket}; /// Result of parsing a single `Protocol` tag. diff --git a/src/configure/read_url.rs b/src/configure/read_url.rs deleted file mode 100644 index d164a7007..000000000 --- a/src/configure/read_url.rs +++ /dev/null @@ -1,44 +0,0 @@ -use anyhow::{anyhow, format_err}; - -use crate::context::Context; -use crate::socks::Socks5Config; - -pub async fn read_url(context: &Context, url: &str) -> anyhow::Result { - match read_url_inner(context, url).await { - Ok(s) => { - info!(context, "Successfully read url {}", url); - Ok(s) - } - Err(e) => { - info!(context, "Can't read URL {}: {:#}", url, e); - Err(format_err!("Can't read URL {}: {:#}", url, e)) - } - } -} - -pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result { - let socks5_config = Socks5Config::from_database(&context.sql).await?; - let client = crate::http::get_client(socks5_config)?; - let mut url = url.to_string(); - - // Follow up to 10 http-redirects - for _i in 0..10 { - let response = client.get(&url).send().await?; - if response.status().is_redirection() { - let headers = response.headers(); - let header = headers - .get_all("location") - .iter() - .last() - .ok_or_else(|| anyhow!("Redirection doesn't have a target location"))? - .to_str()?; - info!(context, "Following redirect to {}", header); - url = header.to_string(); - continue; - } - - return response.text().await.map_err(Into::into); - } - - Err(format_err!("Followed 10 redirections")) -} diff --git a/src/http.rs b/src/http.rs deleted file mode 100644 index 8eed8b55e..000000000 --- a/src/http.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! # HTTP module. - -use std::time::Duration; - -use anyhow::Result; - -use crate::socks::Socks5Config; - -const HTTP_TIMEOUT: Duration = Duration::from_secs(30); - -pub(crate) fn get_client(socks5_config: Option) -> Result { - let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT); - let builder = if let Some(socks5_config) = socks5_config { - let proxy = reqwest::Proxy::all(socks5_config.to_url())?; - builder.proxy(proxy) - } else { - // Disable usage of "system" proxy configured via environment variables. - // It is enabled by default in `reqwest`, see - // - // for documentation. - builder.no_proxy() - }; - Ok(builder.build()?) -} diff --git a/src/lib.rs b/src/lib.rs index 51a72a1d5..23947813c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,6 @@ mod decrypt; pub mod download; mod e2ee; pub mod ephemeral; -mod http; mod imap; pub mod imex; mod scheduler; @@ -104,7 +103,7 @@ mod dehtml; mod authres; mod color; pub mod html; -mod net; +pub mod net; pub mod plaintext; pub mod summary; diff --git a/src/net.rs b/src/net.rs index a0f3a3313..d4d529770 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,4 +1,4 @@ -///! # Common network utilities. +//! # Common network utilities. use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; use std::str::FromStr; @@ -12,9 +12,12 @@ use tokio_io_timeout::TimeoutStream; use crate::context::Context; use crate::tools::time; +pub(crate) mod http; pub(crate) mod session; pub(crate) mod tls; +pub use http::{read_url, read_url_blob}; + async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result { let tcp_stream = timeout(timeout_val, TcpStream::connect(addr)) .await diff --git a/src/net/http.rs b/src/net/http.rs new file mode 100644 index 000000000..3dd49b40a --- /dev/null +++ b/src/net/http.rs @@ -0,0 +1,62 @@ +//! # HTTP module. + +use std::time::Duration; + +use anyhow::{anyhow, Result}; + +use crate::context::Context; +use crate::socks::Socks5Config; + +const HTTP_TIMEOUT: Duration = Duration::from_secs(30); + +/// Retrieves the text contents of URL using HTTP GET request. +pub async fn read_url(context: &Context, url: &str) -> Result { + Ok(read_url_inner(context, url).await?.text().await?) +} + +/// Retrieves the binary contents of URL using HTTP GET request. +pub async fn read_url_blob(context: &Context, url: &str) -> Result> { + Ok(read_url_inner(context, url).await?.bytes().await?.into()) +} + +async fn read_url_inner(context: &Context, url: &str) -> Result { + let socks5_config = Socks5Config::from_database(&context.sql).await?; + let client = get_client(socks5_config)?; + let mut url = url.to_string(); + + // Follow up to 10 http-redirects + for _i in 0..10 { + let response = client.get(&url).send().await?; + if response.status().is_redirection() { + let headers = response.headers(); + let header = headers + .get_all("location") + .iter() + .last() + .ok_or_else(|| anyhow!("Redirection doesn't have a target location"))? + .to_str()?; + info!(context, "Following redirect to {}", header); + url = header.to_string(); + continue; + } + + return Ok(response); + } + + Err(anyhow!("Followed 10 redirections")) +} + +pub(crate) fn get_client(socks5_config: Option) -> Result { + let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT); + let builder = if let Some(socks5_config) = socks5_config { + let proxy = reqwest::Proxy::all(socks5_config.to_url())?; + builder.proxy(proxy) + } else { + // Disable usage of "system" proxy configured via environment variables. + // It is enabled by default in `reqwest`, see + // + // for documentation. + builder.no_proxy() + }; + Ok(builder.build()?) +} diff --git a/src/oauth2.rs b/src/oauth2.rs index 2fe8795b4..f3e5dfe5e 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -160,7 +160,7 @@ pub(crate) async fn get_oauth2_access_token( // ... and POST let socks5_config = Socks5Config::from_database(&context.sql).await?; - let client = crate::http::get_client(socks5_config)?; + let client = crate::net::http::get_client(socks5_config)?; let response: Response = match client.post(post_url).form(&post_param).send().await { Ok(resp) => match resp.json().await { @@ -291,7 +291,7 @@ impl Oauth2 { // "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg" // } let socks5_config = Socks5Config::from_database(&context.sql).await.ok()?; - let client = match crate::http::get_client(socks5_config) { + let client = match crate::net::http::get_client(socks5_config) { Ok(cl) => cl, Err(err) => { warn!(context, "failed to get HTTP client: {}", err); diff --git a/src/qr.rs b/src/qr.rs index 920776b5e..7bbe73ce6 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -534,7 +534,7 @@ struct CreateAccountErrorResponse { async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> { let url_str = &qr[DCACCOUNT_SCHEME.len()..]; let socks5_config = Socks5Config::from_database(&context.sql).await?; - let response = crate::http::get_client(socks5_config)? + let response = crate::net::http::get_client(socks5_config)? .post(url_str) .send() .await?;