From 72de4e228394826e7d90e24f5794242523389d10 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Fri, 9 Oct 2020 20:24:22 -0300 Subject: [PATCH 1/4] refactor: handlers and routes --- CHANGELOG.md | 9 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 1 - .../{static_fs => file_explorer}/handler.rs | 44 +++++----- .../{static_fs => file_explorer}/html/mod.rs | 0 .../html/static/index.html | 0 .../html/static/style.css | 0 .../{static_fs => file_explorer}/mod.rs | 0 src/handler/main_handler.rs | 17 ++++ src/handler/mod.rs | 6 +- src/main.rs | 87 ------------------- src/{server.rs => server/http.rs} | 14 ++- src/server/mod.rs | 3 + 14 files changed, 65 insertions(+), 120 deletions(-) create mode 100644 CHANGELOG.md rename src/handler/{static_fs => file_explorer}/handler.rs (59%) rename src/handler/{static_fs => file_explorer}/html/mod.rs (100%) rename src/handler/{static_fs => file_explorer}/html/static/index.html (100%) rename src/handler/{static_fs => file_explorer}/html/static/style.css (100%) rename src/handler/{static_fs => file_explorer}/mod.rs (100%) create mode 100644 src/handler/main_handler.rs rename src/{server.rs => server/http.rs} (72%) create mode 100644 src/server/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5056567c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changes + +## v0.0.9 + +### Added + +* [CLI](https://github.com/EstebanBorai/http-server/commit/f7ea5b278e8b46c322d82c324c7f263ecae3914b) +* [Config Shape](https://github.com/EstebanBorai/http-server/commit/49e4cd73ddc4401bf684ad70875b075bb35caf5a) +* [HTTP server with File Explorer](https://github.com/EstebanBorai/http-server/commit/7aa59495dab51c52a0a79b75c185b3b74dda5be1) diff --git a/Cargo.lock b/Cargo.lock index 4dc98492..364a78b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ [[package]] name = "http-server" -version = "0.0.9" +version = "0.0.10" dependencies = [ "ascii", "clap", diff --git a/Cargo.toml b/Cargo.toml index 514de23e..3dcf3c72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http-server" -version = "0.0.9" +version = "0.0.10" authors = ["Esteban Borai "] edition = "2018" license = "MIT" diff --git a/README.md b/README.md index 6b47f6da..4e0463de 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@
[![Crates.io](https://img.shields.io/crates/v/http-server.svg)](https://crates.io/crates/http-server) - [![Documentation](https://docs.rs/http-server/badge.svg)](https://docs.rs/http-server) ![Build](https://github.com/EstebanBorai/http-server/workflows/build/badge.svg) ![Lint](https://github.com/EstebanBorai/http-server/workflows/clippy/fmt/badge.svg) ![Tests](https://github.com/EstebanBorai/http-server/workflows/tests/badge.svg) diff --git a/src/handler/static_fs/handler.rs b/src/handler/file_explorer/handler.rs similarity index 59% rename from src/handler/static_fs/handler.rs rename to src/handler/file_explorer/handler.rs index bff30411..bedb0fb5 100644 --- a/src/handler/static_fs/handler.rs +++ b/src/handler/file_explorer/handler.rs @@ -2,7 +2,7 @@ use crate::file_explorer::FileExplorer; use crate::handler::build_html; use ascii::AsciiString; use std::fs::{read_dir, File}; -use tiny_http::{Request, Response}; +use tiny_http::{Request, Response, ResponseBox}; /// Creates a path by merging the URL params and the `root_dir`. /// Then reads the file system entries in the resulting path. @@ -11,7 +11,7 @@ use tiny_http::{Request, Response}; /// and responds with an HTML with the entry listing. /// /// Otherwise retrieves the provided file. -pub fn static_fs(request: Request, file_explorer: &FileExplorer) { +pub fn file_explorer(request: Request, file_explorer: &FileExplorer) -> (Request, ResponseBox) { match file_explorer.read(request.url().as_ref()) { Ok(entry) => { if entry.is_file { @@ -20,12 +20,16 @@ pub fn static_fs(request: Request, file_explorer: &FileExplorer) { .to_string(); let mime_type = AsciiString::from_ascii(mime_type.as_bytes()).unwrap(); let file = File::open(entry.path).unwrap(); - let response = Response::from_file(file).with_header(tiny_http::Header { - field: "Content-Type".parse().unwrap(), - value: mime_type, - }); - request.respond(response).unwrap(); + ( + request, + Response::from_file(file) + .with_header(tiny_http::Header { + field: "Content-Type".parse().unwrap(), + value: mime_type, + }) + .boxed(), + ) } else { let dirpath = entry.path.clone(); let dirpath = dirpath.to_str().unwrap(); @@ -40,21 +44,21 @@ pub fn static_fs(request: Request, file_explorer: &FileExplorer) { entries, ); let mime_type_value: AsciiString = AsciiString::from_ascii("text/html").unwrap(); + let response = Response::from_string(html) + .with_status_code(200) + .with_header(tiny_http::Header { + field: "Content-Type".parse().unwrap(), + value: mime_type_value, + }); - request - .respond( - Response::from_string(html) - .with_status_code(200) - .with_header(tiny_http::Header { - field: "Content-Type".parse().unwrap(), - value: mime_type_value, - }), - ) - .unwrap() + (request, response.boxed()) } } - Err(_) => request - .respond(Response::from_string("Not Found").with_status_code(404)) - .unwrap(), + Err(_) => ( + request, + Response::from_string("Not Found") + .with_status_code(404) + .boxed(), + ), } } diff --git a/src/handler/static_fs/html/mod.rs b/src/handler/file_explorer/html/mod.rs similarity index 100% rename from src/handler/static_fs/html/mod.rs rename to src/handler/file_explorer/html/mod.rs diff --git a/src/handler/static_fs/html/static/index.html b/src/handler/file_explorer/html/static/index.html similarity index 100% rename from src/handler/static_fs/html/static/index.html rename to src/handler/file_explorer/html/static/index.html diff --git a/src/handler/static_fs/html/static/style.css b/src/handler/file_explorer/html/static/style.css similarity index 100% rename from src/handler/static_fs/html/static/style.css rename to src/handler/file_explorer/html/static/style.css diff --git a/src/handler/static_fs/mod.rs b/src/handler/file_explorer/mod.rs similarity index 100% rename from src/handler/static_fs/mod.rs rename to src/handler/file_explorer/mod.rs diff --git a/src/handler/main_handler.rs b/src/handler/main_handler.rs new file mode 100644 index 00000000..31f84058 --- /dev/null +++ b/src/handler/main_handler.rs @@ -0,0 +1,17 @@ +use crate::file_explorer::FileExplorer; +use crate::handler::file_explorer; +use tiny_http::{Request, Response, ResponseBox}; + +/// The main handler acts like a router for every supported method. +/// If a method is not supported then responds with `Method Not Allowed 405` +pub fn main_handler(req: Request, fexplorer: &FileExplorer) -> (Request, ResponseBox) { + match req.method().to_string().to_lowercase().as_str() { + "get" => file_explorer(req, fexplorer), + _ => ( + req, + Response::from_string("Method Not Allowed") + .with_status_code(405) + .boxed(), + ), + } +} diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 29323f27..0132536c 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -1,3 +1,5 @@ -mod static_fs; +mod file_explorer; +mod main_handler; -pub use static_fs::*; +pub use file_explorer::*; +pub use main_handler::*; diff --git a/src/main.rs b/src/main.rs index c0c5cc35..04e571e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,90 +1,3 @@ -//!
-//!
-//! -//!
-//!

http-server

-//!

Command-line HTTP Server

-//!
-//! -//!
-//! -//! [![Crates.io](https://img.shields.io/crates/v/http-server.svg)](https://crates.io/crates/http-server) -//! [![Documentation](https://docs.rs/http-server/badge.svg)](https://docs.rs/http-server) -//! ![Build](https://github.com/EstebanBorai/http-server/workflows/build/badge.svg) -//! ![Lint](https://github.com/EstebanBorai/http-server/workflows/clippy/fmt/badge.svg) -//! ![Tests](https://github.com/EstebanBorai/http-server/workflows/tests/badge.svg) -//! -//!
-//! -//! ## Index -//! -//! - [Installation](#installation) -//! - [Usage](#usage) -//! - [Flags](#flags) -//! - [Options](#options) -//! - [Contributing](#contributing) -//! - [License](#license) -//! - [Contribution](#contribution) -//! -//! ## Installation -//! -//! ```bash -//! cargo install http-server -//! ``` -//! -//! Check for the installation to be successful. -//! -//! ```bash -//! http-server --help -//! ``` -//! -//! ## Usage -//! -//! ``` -//! http-server [FLAGS] [OPTIONS] [root_dir] -//! ``` -//! -//! ### Flags -//! -//! Flags are provided without any values. For example: -//! -//! ``` -//! http-server --help -//! ``` -//! -//! Name | Short | Long | Description -//! --- | --- | --- | --- -//! Help | `h` | `help` | Prints help information -//! Version | `V` | `version` | Prints version information -//! -//! ### Options -//! -//! Options are provided with a value and also have default values. For example: -//! -//! ``` -//! http-server --address 127.0.0.1 -//! ``` -//! -//! Name | Short | Long | Description | Default Value -//! --- | --- | --- | --- | --- -//! Address | `a` | `address` | Address to bind the server | `0.0.0.0` -//! Port | `p` | `port` | Port to bind the server | `7878` -//! -//! ## Contributing -//! -//! Every contribution to this project is welcome. Feel free to open a pull request, -//! an issue or just by starting this project. -//! -//! ## License -//! -//! Licensed under the MIT License -//! -//! ### Contribution -//! -//! Unless you explicitly state otherwise, any contribution intentionally submitted for -//! inclusion in http-server by you, shall be dual licensed as above, without any additional -//! terms or conditions. -//! mod cli; mod config; mod file_explorer; diff --git a/src/server.rs b/src/server/http.rs similarity index 72% rename from src/server.rs rename to src/server/http.rs index 3c9bac6a..ce0b0df9 100644 --- a/src/server.rs +++ b/src/server/http.rs @@ -1,9 +1,10 @@ use crate::config::Config; use crate::file_explorer::FileExplorer; -use crate::handler::static_fs; +use crate::handler::main_handler; use std::net::SocketAddr; -use tiny_http::{Response, Server, ServerConfig}; +use tiny_http::{Server, ServerConfig}; +/// HTTP Server instance pub struct HttpServer { pub server: tiny_http::Server, pub address: SocketAddr, @@ -31,6 +32,7 @@ impl From for HttpServer { } impl HttpServer { + /// Binds the server to the specified address and listen for incomming requests pub fn serve(&self) { if self.must_log { println!( @@ -40,12 +42,8 @@ impl HttpServer { } for request in self.server.incoming_requests() { - match request.method().as_str().to_lowercase().as_str() { - "get" => static_fs(request, &self.file_explorer), - _ => request - .respond(Response::from_string("Method Not Allowed").with_status_code(405)) - .unwrap(), - } + let (req, res) = main_handler(request, &self.file_explorer); + req.respond(res).unwrap(); } } } diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 00000000..8e815309 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,3 @@ +mod http; + +pub use http::*; From 54be7c0e2cf00d01962e4186f8952f5e5e7dc138 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Wed, 14 Oct 2020 23:07:05 -0300 Subject: [PATCH 2/4] add: entity tag support Implements an Entity Tag hash by obtaining the date created, the date of the last modification and the size of the file. --- CHANGELOG.md | 6 ++++++ src/config.rs | 2 -- src/handler/file_explorer/handler.rs | 11 +++++++++-- src/server/etag.rs | 17 +++++++++++++++++ src/server/mod.rs | 2 ++ 5 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 src/server/etag.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5056567c..7f36bcc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changes +## v0.0.10 + +### Added + +* [Etag Support](https://github.com/EstebanBorai/http-server/commit/32dc9a2e6b32bb18906ef06d45c155731d91519e) + ## v0.0.9 ### Added diff --git a/src/config.rs b/src/config.rs index 63811990..d2e26630 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,8 +28,6 @@ impl From> for Config { }; let silent = matches.is_present(SILENT.1); - - // at this point the values provided to the config are validated by the CLI Self { address, port, diff --git a/src/handler/file_explorer/handler.rs b/src/handler/file_explorer/handler.rs index bedb0fb5..30e7543c 100644 --- a/src/handler/file_explorer/handler.rs +++ b/src/handler/file_explorer/handler.rs @@ -1,5 +1,6 @@ use crate::file_explorer::FileExplorer; use crate::handler::build_html; +use crate::server::make_entity_tag; use ascii::AsciiString; use std::fs::{read_dir, File}; use tiny_http::{Request, Response, ResponseBox}; @@ -16,10 +17,12 @@ pub fn file_explorer(request: Request, file_explorer: &FileExplorer) -> (Request Ok(entry) => { if entry.is_file { let mime_type = mime_guess::from_path(&entry.path) - .first_or_octet_stream() - .to_string(); + .first_or_octet_stream() + .to_string(); let mime_type = AsciiString::from_ascii(mime_type.as_bytes()).unwrap(); let file = File::open(entry.path).unwrap(); + let entity_tag = make_entity_tag(&file.metadata().unwrap()); + let entity_tag = AsciiString::from_ascii(entity_tag).unwrap(); ( request, @@ -28,6 +31,10 @@ pub fn file_explorer(request: Request, file_explorer: &FileExplorer) -> (Request field: "Content-Type".parse().unwrap(), value: mime_type, }) + .with_header(tiny_http::Header { + field: "Etag".parse().unwrap(), + value: entity_tag + }) .boxed(), ) } else { diff --git a/src/server/etag.rs b/src/server/etag.rs new file mode 100644 index 00000000..88abc545 --- /dev/null +++ b/src/server/etag.rs @@ -0,0 +1,17 @@ +use std::fs::Metadata; +use std::time::SystemTime; +use std::hash::{Hash, Hasher}; +use std::collections::hash_map::DefaultHasher; + +/// Creates an Entity Tag from a `File` `Metadata` +pub fn make_entity_tag(meta: &Metadata) -> String { + let mut created_hasher = DefaultHasher::new(); + let mut modified_hasher = DefaultHasher::new(); + let mut size_hasher = DefaultHasher::new(); + + meta.len().hash(&mut size_hasher); + meta.created().unwrap_or(SystemTime::now()).hash(&mut created_hasher); + meta.modified().unwrap_or(SystemTime::now()).hash(&mut modified_hasher); + + format!("{0:x}{1:x}{2:x}", created_hasher.finish(), modified_hasher.finish(), size_hasher.finish()) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 8e815309..61a62336 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,3 +1,5 @@ +mod etag; mod http; +pub use etag::*; pub use http::*; From 1a29ebaa99d54709948bc0fd4a90e49375282d7b Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Fri, 16 Oct 2020 23:26:49 -0300 Subject: [PATCH 3/4] wip: entity tag weak --- src/error.rs | 14 +++++ src/handler/file_explorer/handler.rs | 14 +++-- src/main.rs | 1 + src/server/etag.rs | 17 ------ src/server/header/etag.rs | 80 ++++++++++++++++++++++++++++ src/server/header/http_header.rs | 19 +++++++ src/server/header/mod.rs | 5 ++ src/server/mod.rs | 4 +- 8 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 src/error.rs delete mode 100644 src/server/etag.rs create mode 100644 src/server/header/etag.rs create mode 100644 src/server/header/http_header.rs create mode 100644 src/server/header/mod.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..b5354a65 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,14 @@ +#[derive(Debug)] +pub struct HttpError { + pub status_code: u16, + pub message: String, +} + +impl HttpError { + pub fn new(status_code: u16, message: &str) -> Self { + Self { + status_code, + message: message.to_string(), + } + } +} diff --git a/src/handler/file_explorer/handler.rs b/src/handler/file_explorer/handler.rs index 30e7543c..cee464c2 100644 --- a/src/handler/file_explorer/handler.rs +++ b/src/handler/file_explorer/handler.rs @@ -1,9 +1,9 @@ use crate::file_explorer::FileExplorer; use crate::handler::build_html; -use crate::server::make_entity_tag; +use crate::server::{ETag, HttpHeader}; use ascii::AsciiString; use std::fs::{read_dir, File}; -use tiny_http::{Request, Response, ResponseBox}; +use tiny_http::{Header, Request, Response, ResponseBox}; /// Creates a path by merging the URL params and the `root_dir`. /// Then reads the file system entries in the resulting path. @@ -21,8 +21,9 @@ pub fn file_explorer(request: Request, file_explorer: &FileExplorer) -> (Request .to_string(); let mime_type = AsciiString::from_ascii(mime_type.as_bytes()).unwrap(); let file = File::open(entry.path).unwrap(); - let entity_tag = make_entity_tag(&file.metadata().unwrap()); - let entity_tag = AsciiString::from_ascii(entity_tag).unwrap(); + let entity_tag = ETag::from_metadata(&file.metadata().unwrap()).unwrap(); + let entity_tag: HttpHeader = entity_tag.into(); + let entity_tag: Header = entity_tag.into(); ( request, @@ -31,10 +32,7 @@ pub fn file_explorer(request: Request, file_explorer: &FileExplorer) -> (Request field: "Content-Type".parse().unwrap(), value: mime_type, }) - .with_header(tiny_http::Header { - field: "Etag".parse().unwrap(), - value: entity_tag - }) + .with_header(entity_tag) .boxed(), ) } else { diff --git a/src/main.rs b/src/main.rs index 04e571e2..894856f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod cli; mod config; +mod error; mod file_explorer; mod handler; mod server; diff --git a/src/server/etag.rs b/src/server/etag.rs deleted file mode 100644 index 88abc545..00000000 --- a/src/server/etag.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::fs::Metadata; -use std::time::SystemTime; -use std::hash::{Hash, Hasher}; -use std::collections::hash_map::DefaultHasher; - -/// Creates an Entity Tag from a `File` `Metadata` -pub fn make_entity_tag(meta: &Metadata) -> String { - let mut created_hasher = DefaultHasher::new(); - let mut modified_hasher = DefaultHasher::new(); - let mut size_hasher = DefaultHasher::new(); - - meta.len().hash(&mut size_hasher); - meta.created().unwrap_or(SystemTime::now()).hash(&mut created_hasher); - meta.modified().unwrap_or(SystemTime::now()).hash(&mut modified_hasher); - - format!("{0:x}{1:x}{2:x}", created_hasher.finish(), modified_hasher.finish(), size_hasher.finish()) -} diff --git a/src/server/header/etag.rs b/src/server/header/etag.rs new file mode 100644 index 00000000..652d9edd --- /dev/null +++ b/src/server/header/etag.rs @@ -0,0 +1,80 @@ +use crate::server::header::HttpHeader; +use std::fs::Metadata; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// The ETag response-header field provides the current value of the entity tag +/// for the requested variant. The headers used with entity tags are described in +/// sections 14.24, 14.26 and 14.44. The entity tag MAY be used for comparison +/// with other entities from the same resource (see section 13.3.3). +/// +/// # Anatomy +/// +/// The value of an ETag must follow the ABNF form: +/// +/// ``` +/// entity-tag = [ weak ] opaque-tag +/// +/// weak = "W/" +/// opaque-tag = quoted-string +/// ``` +/// +/// Reference: [US-ASCII Coded Character Set](https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2) +/// +/// The anatomy of an ETag may vary based on its type. +/// An ETag could be either _weak_ or _strong_. +/// +/// A _weak_ ETag is denoted by the prefix `W/`: +/// +/// ``` +/// ETag: W/"557b5a3d3eca3aa493d35e47e94c94a2" +/// ``` +/// +/// In the other hand a _strong_ Etag have no prefix. +/// +/// # References +/// +/// * [W3 RFC2616 - Sec 2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2) +/// * [W3 RFC2616 - Sec 14](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19) +pub struct ETag { + value: String, +} + +impl ETag { + /// Creates an **strong** entity tag from the + /// provided metadata + pub fn from_metadata(meta: &Metadata) -> Result { + let time_created = meta.created().unwrap_or(SystemTime::now()) + .duration_since(UNIX_EPOCH) + .unwrap(); + + let time_modified = meta.modified().unwrap_or(SystemTime::now()) + .duration_since(UNIX_EPOCH) + .unwrap(); + + let tag = format!("{:x}{:x}{:x}", time_created.subsec_millis(), time_modified.subsec_millis(), meta.len()); + + if ETag::is_valid_tag(&tag) { + return Ok(Self { + value: tag + }) + } + + Err(()) + } + + /// Ensures that every character of the tag is a valid character. + /// + /// Every character (in its 7-bit representation) from the Unicode + /// character U+0023 to the U+007E is valid for an ETag hash. + /// Also the exclamation character which is represented by the Unicode + /// code U+0021 and the padding character U+0080. + fn is_valid_tag(slice: &str) -> bool { + slice.bytes().all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) + } +} + +impl Into for ETag { + fn into(self) -> HttpHeader { + HttpHeader(String::from("ETag"), self.value) + } +} diff --git a/src/server/header/http_header.rs b/src/server/header/http_header.rs new file mode 100644 index 00000000..516f66bf --- /dev/null +++ b/src/server/header/http_header.rs @@ -0,0 +1,19 @@ +use ascii::AsciiString; +use tiny_http::Header; + +pub struct HttpHeader(pub String, pub String); + +impl From
for HttpHeader { + fn from(header: Header) -> Self { + Self(header.field.to_string(), header.value.to_string()) + } +} + +impl Into
for HttpHeader { + fn into(self) -> Header { + Header { + field: self.0.parse().unwrap(), + value: AsciiString::from_ascii(self.1).unwrap(), + } + } +} diff --git a/src/server/header/mod.rs b/src/server/header/mod.rs new file mode 100644 index 00000000..d4bf72d4 --- /dev/null +++ b/src/server/header/mod.rs @@ -0,0 +1,5 @@ +mod etag; +mod http_header; + +pub use etag::*; +pub use http_header::*; diff --git a/src/server/mod.rs b/src/server/mod.rs index 61a62336..8290334d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,5 @@ -mod etag; +mod header; mod http; -pub use etag::*; +pub use header::*; pub use http::*; From e414cf1b3948511eaf2c04e2a6bbd6f5c4be155c Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 17 Oct 2020 01:31:41 -0300 Subject: [PATCH 4/4] fmt: format code --- src/server/header/etag.rs | 82 +++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/server/header/etag.rs b/src/server/header/etag.rs index 652d9edd..09c326e5 100644 --- a/src/server/header/etag.rs +++ b/src/server/header/etag.rs @@ -2,6 +2,18 @@ use crate::server::header::HttpHeader; use std::fs::Metadata; use std::time::{SystemTime, UNIX_EPOCH}; +/// Exclamation point (!) 7-bit character representation +const EXCLAMATION_CHARACTER: u8 = b'\x21'; + +/// Hastag (#) 7-bit character representation +const HASHTAG_SYMBOL: u8 = b'\x23'; + +/// Closing Curly Brace (}) 7-bit character representation +const CLOSING_CURLY_BRACE: u8 = b'\x7e'; + +/// Padding character 7-bit representation +const PADDING_CHARACTER: u8 = b'\x80'; + /// The ETag response-header field provides the current value of the entity tag /// for the requested variant. The headers used with entity tags are described in /// sections 14.24, 14.26 and 14.44. The entity tag MAY be used for comparison @@ -36,45 +48,55 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// * [W3 RFC2616 - Sec 2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2) /// * [W3 RFC2616 - Sec 14](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19) pub struct ETag { - value: String, + value: String, } impl ETag { - /// Creates an **strong** entity tag from the - /// provided metadata - pub fn from_metadata(meta: &Metadata) -> Result { - let time_created = meta.created().unwrap_or(SystemTime::now()) - .duration_since(UNIX_EPOCH) - .unwrap(); + /// Creates an **strong** entity tag from the + /// provided metadata + pub fn from_metadata(meta: &Metadata) -> Result { + let time_created = meta + .created() + .unwrap_or(SystemTime::now()) + .duration_since(UNIX_EPOCH) + .unwrap(); - let time_modified = meta.modified().unwrap_or(SystemTime::now()) - .duration_since(UNIX_EPOCH) - .unwrap(); + let time_modified = meta + .modified() + .unwrap_or(SystemTime::now()) + .duration_since(UNIX_EPOCH) + .unwrap(); - let tag = format!("{:x}{:x}{:x}", time_created.subsec_millis(), time_modified.subsec_millis(), meta.len()); + let tag = format!( + "{:x}{:x}{:x}", + time_created.subsec_millis(), + time_modified.subsec_millis(), + meta.len() + ); - if ETag::is_valid_tag(&tag) { - return Ok(Self { - value: tag - }) - } + if ETag::is_valid_tag(&tag) { + return Ok(Self { value: tag }); + } - Err(()) - } + Err(()) + } - /// Ensures that every character of the tag is a valid character. - /// - /// Every character (in its 7-bit representation) from the Unicode - /// character U+0023 to the U+007E is valid for an ETag hash. - /// Also the exclamation character which is represented by the Unicode - /// code U+0021 and the padding character U+0080. - fn is_valid_tag(slice: &str) -> bool { - slice.bytes().all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) - } + /// Ensures that every character of the tag is a valid character. + /// + /// Every character (in its 7-bit representation) from the Unicode + /// character U+0023 to the U+007E is valid for an ETag hash. + /// Also the exclamation character which is represented by the Unicode + /// code U+0021 and the padding character U+0080. + fn is_valid_tag(slice: &str) -> bool { + slice.bytes().all(|c| { + c == EXCLAMATION_CHARACTER + || (c >= HASHTAG_SYMBOL && c <= CLOSING_CURLY_BRACE) | (c >= PADDING_CHARACTER) + }) + } } impl Into for ETag { - fn into(self) -> HttpHeader { - HttpHeader(String::from("ETag"), self.value) - } + fn into(self) -> HttpHeader { + HttpHeader(String::from("ETag"), self.value) + } }