Compare commits

...

10 Commits

Author SHA1 Message Date
Dustin 324ef442a2 Remove @type field from StateChange struct
This field is not included in EventStream messages from Fastmail.
2024-07-21 21:08:57 -05:00
mdecimus 4633f8997e v0.3.2 2023-12-29 10:35:14 +01:00
mdecimus f7920dd84f v0.3.1 2023-12-28 11:47:43 +01:00
mdecimus 1e47fe8d08 Use provided accountId on uploads 2023-11-12 10:25:17 +01:00
mdecimus 75da82b008 Merge branch 'main' of github.com:stalwartlabs/jmap-client 2023-11-01 09:02:18 +01:00
mdecimus 92c1406b4f Bump tokio-tungstenite to 0.20 2023-11-01 09:01:30 +01:00
Mauro D 5b6595e770
Merge pull request #9 from mercxry/add-docs-clientbuilder
Document ClientBuilder
2023-09-05 18:56:45 +02:00
Matteo Martellini c3fc33ee57 docs: ClientBuilder 2023-09-04 00:39:11 +02:00
mdecimus a55af189d4 Added create_with_id method. 2023-07-05 19:00:07 +02:00
Me ab6a9e55c2 Use WebPKI roots 2023-06-26 17:27:08 +02:00
7 changed files with 124 additions and 18 deletions

View File

@ -1,3 +1,7 @@
jmap-client 0.3.2
================================
- Bump to `rustls` 0.22.
jmap-client 0.3.0
================================
- JMAP for Sieve Scripts DRAFT-14 support.

View File

@ -1,7 +1,7 @@
[package]
name = "jmap-client"
description = "JMAP client library for Rust"
version = "0.3.0"
version = "0.3.2"
edition = "2021"
authors = [ "Stalwart Labs Ltd. <hello@stalw.art>"]
license = "Apache-2.0 OR MIT"
@ -13,12 +13,13 @@ readme = "README.md"
resolver = "2"
[dependencies]
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"]}
tokio-tungstenite = { version = "0.19", features = ["rustls-tls-webpki-roots"], optional = true}
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls-webpki-roots"]}
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"], optional = true}
tokio = { version = "1.16", default-features = false, features = ["io-util"], optional = true }
futures-util = { version = "0.3", optional = true}
async-stream = { version = "0.3", optional = true}
rustls = { version = "0.21.0", features = ["dangerous_configuration"], optional = true }
rustls = { version = "0.22", optional = true }
rustls-pki-types = { version = "1" }
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"]}
@ -28,7 +29,7 @@ base64 = "0.13"
maybe-async = "0.2"
[features]
default = ["async"]
default = ["async", "websockets"]
async = ["futures-util", "async-stream", "reqwest/stream"]
websockets = ["tokio", "tokio-tungstenite", "rustls"]
blocking = ["reqwest/blocking", "maybe-async/is_sync"]

View File

@ -87,6 +87,9 @@ impl Default for ClientBuilder {
}
impl ClientBuilder {
/// Creates a new `ClientBuilder`.
///
/// Setting the credentials is required to connect to the JMAP API.
pub fn new() -> Self {
Self {
credentials: None,
@ -97,21 +100,70 @@ impl ClientBuilder {
}
}
/// Set up client credentials to connect to the JMAP API.
///
/// The JMAP API URL is set using the [ClientBuilder.connect()](struct.ClientBuilder.html#method.connect) method.
///
/// # Bearer authentication
/// Pass a `&str` with the API Token.
///
/// ```rust
/// Client::new().credentials("some-api-token");
/// ```
///
/// Or use the longer form by using [Credentials::bearer()](enum.Credentials.html#method.bearer).
/// ```rust
/// let credentials = Credentials::bearer("some-api-token");
/// Client::new().credentials(credentials);
/// ```
///
/// # Basic authentication
/// Pass a `(&str, &str)` tuple, with the first position containing a username and the second containing a password.
///
/// **It is not suggested to use this approach in production;** instead, if possible, use [Bearer authentication](struct.ClientBuilder.html#bearer-authentication).
///
/// ```rust
/// Client::new().credentials(("user@domain.com", "password"));
/// ```
///
/// Or use the longer form by using [Credentials::basic()](enum.Credentials.html#method.basic).
/// ```rust
/// let credentials = Credentials::basic("user@domain.com", "password");
/// Client::new().credentials(credentials);
/// ```
pub fn credentials(mut self, credentials: impl Into<Credentials>) -> Self {
self.credentials = Some(credentials.into());
self
}
/// Set a timeout for all the requests to the JMAP API.
///
/// The timeout can be changed after the `Client` has been created by using [Client.set_timeout()](struct.Client.html#method.set_timeout).
///
/// By default the timeout is 10 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
/// Accepts invalid certificates for all the requests to the JMAP API.
///
/// By default certificates are validated.
///
/// # Warning
/// **It is not suggested to use this approach in production;** this method should be used only for testing and as a last resort.
///
/// [Read more in the reqwest docs](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.danger_accept_invalid_certs)
pub fn accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
self.accept_invalid_certs = accept_invalid_certs;
self
}
/// Set a list of trusted hosts that will be checked when a redirect is required.
///
/// The list can be changed after the `Client` has been created by using [Client.set_follow_redirects()](struct.Client.html#method.set_follow_redirects).
///
/// The client will follow at most 5 redirects.
pub fn follow_redirects(
mut self,
trusted_hosts: impl IntoIterator<Item = impl Into<String>>,
@ -120,6 +172,7 @@ impl ClientBuilder {
self
}
/// Set the originating IP address of the client connecting to the JMAP API.
pub fn forwarded_for(mut self, forwarded_for: IpAddr) -> Self {
self.forwarded_for = Some(match forwarded_for {
IpAddr::V4(addr) => format!("for={}", addr),
@ -128,6 +181,9 @@ impl ClientBuilder {
self
}
/// Connects to the JMAP API Session URL.
///
/// Setting up [Credentials](struct.ClientBuilder.html#method.credentials) must be done before calling this function.
#[maybe_async::maybe_async]
pub async fn connect(self, url: &str) -> crate::Result<Client> {
let authorization = match self.credentials.expect("Missing credentials") {

View File

@ -14,8 +14,8 @@ use std::{pin::Pin, sync::Arc};
use ahash::AHashMap;
use futures_util::{stream::SplitSink, SinkExt, Stream, StreamExt};
use rustls::{
client::{ServerCertVerified, ServerCertVerifier},
Certificate, ClientConfig, ServerName,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
ClientConfig, SignatureScheme,
};
use serde::{Deserialize, Serialize};
use tokio::net::TcpStream;
@ -167,20 +167,56 @@ pub struct WsStream {
}
#[doc(hidden)]
#[derive(Debug)]
struct DummyVerifier;
impl ServerCertVerifier for DummyVerifier {
fn verify_server_cert(
&self,
_e: &Certificate,
_i: &[Certificate],
_sn: &ServerName,
_sc: &mut dyn Iterator<Item = &[u8]>,
_o: &[u8],
_n: std::time::SystemTime,
_end_entity: &rustls_pki_types::CertificateDer<'_>,
_intermediates: &[rustls_pki_types::CertificateDer<'_>],
_server_name: &rustls_pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls_pki_types::UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
impl Client {
@ -206,7 +242,7 @@ impl Client {
false,
Connector::Rustls(Arc::new(
ClientConfig::builder()
.with_safe_defaults()
.dangerous()
.with_custom_certificate_verifier(Arc::new(DummyVerifier {}))
.with_no_client_auth(),
))

View File

@ -189,6 +189,14 @@ impl<O: SetObject> SetRequest<O> {
.unwrap()
}
pub fn create_with_id(&mut self, create_id: impl Into<String>) -> &mut O {
let create_id = create_id.into();
self.create
.get_or_insert_with(AHashMap::new)
.insert(create_id.clone(), O::new(0.into()));
self.create.as_mut().unwrap().get_mut(&create_id).unwrap()
}
pub fn create_item(&mut self, item: O) -> String {
let create_id = self.create.as_ref().map_or(0, |c| c.len());
let create_id_str = format!("c{}", create_id);
@ -430,7 +438,7 @@ impl Display for SetErrorType {
}
pub fn from_timestamp(timestamp: i64) -> DateTime<Utc> {
DateTime::<Utc>::from_utc(
DateTime::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap_or_default(),
Utc,
)

View File

@ -71,7 +71,10 @@ impl Client {
V: IntoIterator<Item = W>,
W: Into<String>,
{
let blob_id = self.upload(None, raw_message, None).await?.take_blob_id();
let blob_id = self
.upload(account_id.into(), raw_message, None)
.await?
.take_blob_id();
let mut request = self.build();
let import_request = request
.import_email()

View File

@ -348,8 +348,6 @@ pub enum StateChangeType {
#[derive(Debug, Deserialize)]
pub struct StateChange {
#[serde(rename = "@type")]
pub type_: StateChangeType,
pub changed: AHashMap<String, AHashMap<TypeState, String>>,
}