mirror of
https://github.com/chatmail/core.git
synced 2026-05-09 01:46:30 +03:00
JSON-RPC: add get_http_blob API
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Add `get_http_blob` JSON-RPC API.
|
||||||
|
|
||||||
## [1.112.7] - 2023-04-17
|
## [1.112.7] - 2023-04-17
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -1610,6 +1610,17 @@ impl CommandApi {
|
|||||||
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
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<String> {
|
||||||
|
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.
|
/// Forward messages to another chat.
|
||||||
///
|
///
|
||||||
/// All types of messages can be forwarded,
|
/// All types of messages can be forwarded,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
mod auto_mozilla;
|
mod auto_mozilla;
|
||||||
mod auto_outlook;
|
mod auto_outlook;
|
||||||
mod read_url;
|
|
||||||
mod server_params;
|
mod server_params;
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Context as _, Result};
|
use anyhow::{bail, ensure, Context as _, Result};
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use quick_xml::events::{BytesStart, Event};
|
use quick_xml::events::{BytesStart, Event};
|
||||||
|
|
||||||
use super::read_url::read_url;
|
|
||||||
use super::{Error, ServerParams};
|
use super::{Error, ServerParams};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
|
use crate::net::read_url;
|
||||||
use crate::provider::{Protocol, Socket};
|
use crate::provider::{Protocol, Socket};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ use std::io::BufRead;
|
|||||||
|
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
|
|
||||||
use super::read_url::read_url;
|
|
||||||
use super::{Error, ServerParams};
|
use super::{Error, ServerParams};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::net::read_url;
|
||||||
use crate::provider::{Protocol, Socket};
|
use crate::provider::{Protocol, Socket};
|
||||||
|
|
||||||
/// Result of parsing a single `Protocol` tag.
|
/// Result of parsing a single `Protocol` tag.
|
||||||
|
|||||||
@@ -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<String> {
|
|
||||||
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<String> {
|
|
||||||
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"))
|
|
||||||
}
|
|
||||||
24
src/http.rs
24
src/http.rs
@@ -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<Socks5Config>) -> Result<reqwest::Client> {
|
|
||||||
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
|
|
||||||
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
|
|
||||||
// for documentation.
|
|
||||||
builder.no_proxy()
|
|
||||||
};
|
|
||||||
Ok(builder.build()?)
|
|
||||||
}
|
|
||||||
@@ -68,7 +68,6 @@ mod decrypt;
|
|||||||
pub mod download;
|
pub mod download;
|
||||||
mod e2ee;
|
mod e2ee;
|
||||||
pub mod ephemeral;
|
pub mod ephemeral;
|
||||||
mod http;
|
|
||||||
mod imap;
|
mod imap;
|
||||||
pub mod imex;
|
pub mod imex;
|
||||||
mod scheduler;
|
mod scheduler;
|
||||||
@@ -104,7 +103,7 @@ mod dehtml;
|
|||||||
mod authres;
|
mod authres;
|
||||||
mod color;
|
mod color;
|
||||||
pub mod html;
|
pub mod html;
|
||||||
mod net;
|
pub mod net;
|
||||||
pub mod plaintext;
|
pub mod plaintext;
|
||||||
pub mod summary;
|
pub mod summary;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
///! # Common network utilities.
|
//! # Common network utilities.
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -12,9 +12,12 @@ use tokio_io_timeout::TimeoutStream;
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::tools::time;
|
use crate::tools::time;
|
||||||
|
|
||||||
|
pub(crate) mod http;
|
||||||
pub(crate) mod session;
|
pub(crate) mod session;
|
||||||
pub(crate) mod tls;
|
pub(crate) mod tls;
|
||||||
|
|
||||||
|
pub use http::{read_url, read_url_blob};
|
||||||
|
|
||||||
async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<TcpStream> {
|
async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<TcpStream> {
|
||||||
let tcp_stream = timeout(timeout_val, TcpStream::connect(addr))
|
let tcp_stream = timeout(timeout_val, TcpStream::connect(addr))
|
||||||
.await
|
.await
|
||||||
|
|||||||
62
src/net/http.rs
Normal file
62
src/net/http.rs
Normal file
@@ -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<String> {
|
||||||
|
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<Vec<u8>> {
|
||||||
|
Ok(read_url_inner(context, url).await?.bytes().await?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_url_inner(context: &Context, url: &str) -> Result<reqwest::Response> {
|
||||||
|
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<Socks5Config>) -> Result<reqwest::Client> {
|
||||||
|
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
|
||||||
|
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
|
||||||
|
// for documentation.
|
||||||
|
builder.no_proxy()
|
||||||
|
};
|
||||||
|
Ok(builder.build()?)
|
||||||
|
}
|
||||||
@@ -160,7 +160,7 @@ pub(crate) async fn get_oauth2_access_token(
|
|||||||
|
|
||||||
// ... and POST
|
// ... and POST
|
||||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
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 {
|
let response: Response = match client.post(post_url).form(&post_param).send().await {
|
||||||
Ok(resp) => match resp.json().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"
|
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
|
||||||
// }
|
// }
|
||||||
let socks5_config = Socks5Config::from_database(&context.sql).await.ok()?;
|
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,
|
Ok(cl) => cl,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(context, "failed to get HTTP client: {}", err);
|
warn!(context, "failed to get HTTP client: {}", err);
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ struct CreateAccountErrorResponse {
|
|||||||
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
|
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
|
||||||
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
|
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
|
||||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
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)
|
.post(url_str)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user