diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9fd45e0..3277a4a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,5 +18,7 @@ jobs: - uses: actions/checkout@v4 - name: Build run: cargo build --verbose + - name: Clippy + run: cargo clippy --verbose - name: Run tests run: cargo test --verbose diff --git a/Cargo.lock b/Cargo.lock index 19dc86a..695caf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,12 +40,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bytes" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" - [[package]] name = "cfg-if" version = "1.0.0" @@ -123,15 +117,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -157,51 +142,18 @@ dependencies = [ "digest", ] -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "proc-macro2" version = "1.0.86" @@ -304,71 +256,28 @@ dependencies = [ [[package]] name = "thumbor" -version = "0.0.4" +version = "0.1.0" dependencies = [ "base64ct", "bon", "hmac", - "http", "sha1", "strum", "thiserror", - "url", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 6072e63..460fcc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "thumbor" description = "A Rust client for the Thumbor image service" -version = "0.0.4" +version = "0.1.0" repository = "https://github.com/SteelAlloy/thumbor-rs" documentation = "https://docs.rs/thumbor" readme = "README.md" @@ -20,10 +20,8 @@ cargo = "deny" pedantic = "deny" [dependencies] -url = "2.5.2" strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" -http = "1.1.0" hmac = "0.12.1" sha1 = "0.10.6" base64ct = { version = "1.6.0", features = ["alloc"] } diff --git a/README.md b/README.md index 2fc29c4..d105ffa 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,29 @@ ## Usage +In short : + +```rust +use thumbor::Server; + +let url = Server::new("http://localhost:8888", "my-security-key") + .unwrap() + .settings_builder() + .resize((300, 200)) + .smart(true) + .build() + .to_url("path/to/my/image.jpg"); +``` + ```rust use thumbor::Server; -let server = Server::new_secured("http://localhost:8888", "my-security-key"); +let server = Server::new("http://localhost:8888", "my-security-key").unwrap(); -let builder = server.url_builder() +let settings_builder = server.settings_builder() .resize((300, 200)) .smart(true) .build(); -let url = builder.build("/path/to/my/image.jpg"); +let url = settings_builder.to_url("path/to/my/image.jpg"); ``` diff --git a/src/error.rs b/src/error.rs index 66f9678..43779ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,2 @@ #[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("{0}")] - UrlParseError(#[from] url::ParseError), - #[error("URL cannot be a base")] - UrlCannotBeABase, -} +pub enum Error {} diff --git a/src/lib.rs b/src/lib.rs index dfaaed5..b9ff8df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(clippy::unwrap_used)] + pub mod error; pub mod geometry; mod server; diff --git a/src/server.rs b/src/server.rs index 26a9961..8c1456f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,5 @@ use crate::settings::{Settings, SettingsBuilder}; -use hmac::Hmac; +use hmac::{digest::InvalidLength, Hmac, Mac}; use sha1::Sha1; pub type HmacSha1 = Hmac; @@ -8,20 +8,22 @@ pub type HmacSha1 = Hmac; pub enum Security { #[default] Unsafe, - Hmac(String), + Hmac(HmacSha1), } -impl From for Security { - fn from(value: String) -> Self { - Security::Hmac(value) +impl TryFrom for Security { + type Error = InvalidLength; + + fn try_from(value: String) -> Result { + let hmac = HmacSha1::new_from_slice(value.as_bytes())?; + Ok(Security::Hmac(hmac)) } } /// ``` /// use thumbor::Server; -/// -/// let server = Server::new_secured("http://localhost:8888", "my-security-key"); -/// let server = Server::new_unsafe("http://localhost:8888"); // Don't use this in production ! +/// +/// let server = Server::new("http://localhost:8888", "my-security-key").unwrap(); /// ``` #[derive(Default, Clone)] pub struct Server { @@ -30,20 +32,34 @@ pub struct Server { } impl Server { - pub fn new_unsafe(origin: impl Into) -> Self { - Server { + pub fn new(origin: impl Into, key: impl Into) -> Result { + Ok(Server { origin: origin.into(), - security: Security::Unsafe, - } + security: key.into().try_into()?, + }) } - pub fn new_secured(origin: impl Into, key: impl Into) -> Self { + + /// ``` + /// use thumbor::Server; + /// + /// // Don't use this in production ! + /// let server = Server::new_unsafe("http://localhost:8888"); + /// ``` + pub fn new_unsafe(origin: impl Into) -> Self { Server { origin: origin.into(), - security: Security::Hmac(key.into()), + security: Security::Unsafe, } } - pub fn url_builder(&self) -> SettingsBuilder { + /// Create a new SettingsBuilder with the current Server. + /// ``` + /// use thumbor::Server; + /// + /// let server = Server::new("http://localhost:8888", "my-security-key").unwrap(); + /// let builder = server.settings_builder(); + /// ``` + pub fn settings_builder(&self) -> SettingsBuilder { Settings::with_server(self.clone()) } } diff --git a/src/settings/builder.rs b/src/settings/builder.rs index 66add92..ae21126 100644 --- a/src/settings/builder.rs +++ b/src/settings/builder.rs @@ -1,13 +1,7 @@ +use super::{FitIn, ResponseMode, Settings, Trim}; +use crate::server::Security; use base64ct::{Base64Url, Encoding}; use hmac::Mac; -use http::Uri; - -use crate::{ - error::Error, - server::{HmacSha1, Security}, -}; - -use super::{FitIn, ResponseMode, Settings, Trim}; impl Settings { fn build_path(&self, image_uri: &str) -> String { @@ -80,36 +74,24 @@ impl Settings { path.join("/") } - /// # Errors - /// - `Error::UrlParseError`: URL parsing failed. - /// - `Error::UrlCannotBeABase`: this URL is cannot-be-a-base. - /// # Panics - /// TODO: doc - pub fn build(&self, image_uri: &str) -> Result { + pub fn to_path(&self, image_uri: &str) -> String { let path = self.build_path(image_uri); let security = match &self.server.security { - Security::Unsafe => "unsafe".to_owned(), - Security::Hmac(secret_key) => { - let mut mac = HmacSha1::new_from_slice(secret_key.as_bytes()).unwrap(); - + Security::Unsafe => "unsafe", + Security::Hmac(hmac) => { + let mut mac = hmac.clone(); mac.update(path.as_bytes()); let signature = mac.finalize().into_bytes(); - - Base64Url::encode_string(&signature) + &Base64Url::encode_string(&signature) } }; - Ok(format!("{}/{}/{}", self.server.origin, security, path)) + format!("/{security}/{path}") } - /// # Errors - /// - `Error::UrlParseError`: URL parsing failed. - /// - `Error::UrlCannotBeABase`: this URL is cannot-be-a-base. - /// # Panics - /// TODO: doc - pub fn build_uri(&self, image_uri: &str) -> Result { - Ok(self.build(image_uri).unwrap().parse::().unwrap()) + pub fn to_url(&self, image_uri: &str) -> String { + format!("{}{}", self.server.origin, self.to_path(image_uri)) } } diff --git a/src/tests.rs b/src/tests.rs index 27235a3..bf422cd 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -8,19 +8,20 @@ use crate::{ const TEST_BASE: &str = "http://my.server.com"; const SECURITY_KEY: &str = "my-security-key"; const IMAGE_PATH: &str = "my.server.com/some/path/to/image.jpg"; -static SERVER: LazyLock = LazyLock::new(|| Server::new_secured(TEST_BASE, SECURITY_KEY)); +static SERVER: LazyLock = + LazyLock::new(|| Server::new(TEST_BASE, SECURITY_KEY).expect("Server creation failed")); #[test] fn signing_of_a_known_url_results() { let width = 300; let height = 200; - let builder = SERVER.url_builder().resize((width, height)).build(); + let builder = SERVER.settings_builder().resize((width, height)).build(); - let uri = builder.build_uri(IMAGE_PATH).unwrap(); + let path = builder.to_path(IMAGE_PATH); assert_eq!( - uri.path(), + path, "/8ammJH8D-7tXy6kU3lTvoXlhu4o=/300x200/my.server.com/some/path/to/image.jpg" ); } @@ -28,38 +29,38 @@ fn signing_of_a_known_url_results() { #[test] fn signature_with_meta() { let builder = SERVER - .url_builder() + .settings_builder() .response(ResponseMode::Metadata) .build(); - let uri = builder.build_uri(IMAGE_PATH).unwrap(); + let path = builder.to_path(IMAGE_PATH); assert_eq!( - uri.path(), + path, "/Ps3ORJDqxlSQ8y00T29GdNAh2CY=/meta/my.server.com/some/path/to/image.jpg" ); } #[test] fn signature_with_smart() { - let builder = SERVER.url_builder().smart(true).build(); + let builder = SERVER.settings_builder().smart(true).build(); - let uri = builder.build_uri(IMAGE_PATH).unwrap(); + let path = builder.to_path(IMAGE_PATH); assert_eq!( - uri.path(), + path, "/-2NHpejRK2CyPAm61FigfQgJBxw=/smart/my.server.com/some/path/to/image.jpg" ); } #[test] fn signature_with_fit_in() { - let builder = SERVER.url_builder().fit_in(FitIn::Default).build(); + let builder = SERVER.settings_builder().fit_in(FitIn::Default).build(); - let uri = builder.build_uri(IMAGE_PATH).unwrap(); + let path = builder.to_path(IMAGE_PATH); assert_eq!( - uri.path(), + path, "/uvLnA6TJlF-Cc-L8z9pEtfasO3s=/fit-in/my.server.com/some/path/to/image.jpg" ); } @@ -67,11 +68,14 @@ fn signature_with_fit_in() { #[test] fn signature_with_filters() { let builder = SERVER - .url_builder() + .settings_builder() .filters([Filter::Brightness(10), Filter::Contrast(20)]) .build(); - let uri = builder.build_uri(IMAGE_PATH).unwrap(); + let path = builder.to_path(IMAGE_PATH); - assert_eq!(uri.path(), "/ZZtPCw-BLYN1g42Kh8xTcRs0Qls=/filters:brightness(10):contrast(20)/my.server.com/some/path/to/image.jpg"); + assert_eq!( + path, + "/ZZtPCw-BLYN1g42Kh8xTcRs0Qls=/filters:brightness(10):contrast(20)/my.server.com/some/path/to/image.jpg" + ); }