From f68a2fc387ba07d68ac7c7a3c8cc216172417968 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 19 Apr 2023 23:33:19 +0000 Subject: [PATCH] JSON-RPC: return mimetype and encoding for HTTP blobs --- CHANGELOG.md | 2 +- Cargo.lock | 5 ++-- Cargo.toml | 1 + deltachat-jsonrpc/src/api/mod.rs | 11 ++++---- deltachat-jsonrpc/src/api/types/http.rs | 29 ++++++++++++++++++++ deltachat-jsonrpc/src/api/types/mod.rs | 1 + src/net.rs | 2 +- src/net/http.rs | 36 +++++++++++++++++++++++-- 8 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 deltachat-jsonrpc/src/api/types/http.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e78974b..656f583af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased ### Changes -- Add `get_http_blob` JSON-RPC API. +- Add `get_http_response` JSON-RPC API. ## [1.112.7] - 2023-04-17 diff --git a/Cargo.lock b/Cargo.lock index 87198de03..1d9b43fb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1080,6 +1080,7 @@ dependencies = [ "libc", "log", "mailparse", + "mime", "num-derive", "num-traits", "num_cpus", @@ -2591,9 +2592,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" diff --git a/Cargo.toml b/Cargo.toml index cbd021e2c..e3519b421 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ kamadak-exif = "0.5" lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } libc = "0.2" mailparse = "0.14" +mime = "0.3.17" num_cpus = "1.15" num-derive = "0.3" num-traits = "0.2" diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index de51d2b37..d161dcd48 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -43,6 +43,7 @@ use types::account::Account; use types::chat::FullChat; use types::chat_list::ChatListEntry; use types::contact::ContactObject; +use types::http::HttpResponse; use types::message::MessageData; use types::message::MessageObject; use types::provider_info::ProviderInfo; @@ -1610,15 +1611,13 @@ impl CommandApi { Ok(general_purpose::STANDARD_NO_PAD.encode(blob)) } - /// Makes an HTTP GET request and returns base64-encoded contents. + /// Makes an HTTP GET request and returns a response. /// /// `url` is the HTTP or HTTPS URL. - async fn get_http_blob(&self, account_id: u32, url: String) -> Result { + async fn get_http_response(&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)) + let response = deltachat::net::read_url_blob(&ctx, &url).await?.into(); + Ok(response) } /// Forward messages to another chat. diff --git a/deltachat-jsonrpc/src/api/types/http.rs b/deltachat-jsonrpc/src/api/types/http.rs new file mode 100644 index 000000000..3b9d29509 --- /dev/null +++ b/deltachat-jsonrpc/src/api/types/http.rs @@ -0,0 +1,29 @@ +use deltachat::net::HttpResponse as CoreHttpResponse; +use serde::Serialize; +use typescript_type_def::TypeDef; + +#[derive(Serialize, TypeDef)] +pub struct HttpResponse { + /// base64-encoded response body. + blob: String, + + /// MIME type, e.g. "text/plain" or "text/html". + mimetype: Option, + + /// Encoding, e.g. "utf-8". + encoding: Option, +} + +impl From for HttpResponse { + fn from(response: CoreHttpResponse) -> Self { + use base64::{engine::general_purpose, Engine as _}; + let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob); + let mimetype = response.mimetype; + let encoding = response.encoding; + HttpResponse { + blob, + mimetype, + encoding, + } + } +} diff --git a/deltachat-jsonrpc/src/api/types/mod.rs b/deltachat-jsonrpc/src/api/types/mod.rs index 2d783990e..ace49f14e 100644 --- a/deltachat-jsonrpc/src/api/types/mod.rs +++ b/deltachat-jsonrpc/src/api/types/mod.rs @@ -2,6 +2,7 @@ pub mod account; pub mod chat; pub mod chat_list; pub mod contact; +pub mod http; pub mod location; pub mod message; pub mod provider_info; diff --git a/src/net.rs b/src/net.rs index d4d529770..3e80c40e8 100644 --- a/src/net.rs +++ b/src/net.rs @@ -16,7 +16,7 @@ pub(crate) mod http; pub(crate) mod session; pub(crate) mod tls; -pub use http::{read_url, read_url_blob}; +pub use http::{read_url, read_url_blob, Response as HttpResponse}; async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result { let tcp_stream = timeout(timeout_val, TcpStream::connect(addr)) diff --git a/src/net/http.rs b/src/net/http.rs index 3dd49b40a..cbee57959 100644 --- a/src/net/http.rs +++ b/src/net/http.rs @@ -3,20 +3,52 @@ use std::time::Duration; use anyhow::{anyhow, Result}; +use mime::Mime; use crate::context::Context; use crate::socks::Socks5Config; const HTTP_TIMEOUT: Duration = Duration::from_secs(30); +/// HTTP(S) GET response. +#[derive(Debug)] +pub struct Response { + /// Response body. + pub blob: Vec, + + /// MIME type exntracted from the `Content-Type` header, if any. + pub mimetype: Option, + + /// Encoding extracted from the `Content-Type` header, if any. + pub encoding: Option, +} + /// 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()) +pub async fn read_url_blob(context: &Context, url: &str) -> Result { + let response = read_url_inner(context, url).await?; + let content_type = response + .headers() + .get(reqwest::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.parse::().ok()); + let mimetype = content_type + .as_ref() + .map(|mime| mime.essence_str().to_string()); + let encoding = content_type.as_ref().and_then(|mime| { + mime.get_param(mime::CHARSET) + .map(|charset| charset.as_str().to_string()) + }); + let blob: Vec = response.bytes().await?.into(); + Ok(Response { + blob, + mimetype, + encoding, + }) } async fn read_url_inner(context: &Context, url: &str) -> Result {