Skip to content

Commit

Permalink
16.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
bohdaq committed Aug 10, 2023
1 parent da5211f commit d36db74
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 11 deletions.
Empty file added .cargo/config
Empty file.
2 changes: 2 additions & 0 deletions .cargo/config_arm_linux
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "aarch64-unknown-linux-gnu"
2 changes: 2 additions & 0 deletions .cargo/config_arm_macos
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "aarch64-apple-darwin"
2 changes: 2 additions & 0 deletions .cargo/config_arm_windows
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "aarch64-pc-windows-msvc"
2 changes: 2 additions & 0 deletions .cargo/config_x86_64_linux
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "x86_64-unknown-linux-gnu"
2 changes: 2 additions & 0 deletions .cargo/config_x86_64_macos
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "x86_64-apple-darwin"
2 changes: 2 additions & 0 deletions .cargo/config_x86_64_windows
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "x86_64-pc-windows-msvc"
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-web-server"
version = "15.1.0"
version = "16.0.0"
authors = ["Bohdan Tsap <bohdan.tsap@tutanota.com>"]
repository = "https://github.com/bohdaq/rust-web-framework/"
description = "Collection of utility functions used to build Rust Web and TLS Server. Can be useful while developing HTTP related functionality"
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ Core lib for [rust-web-server](https://github.com/bohdaq/rust-web-server), [rust
**NOTE! The corresponding crate is called [rust-web-server](https://crates.io/crates/rust-web-server)**.

## Features
1. [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
1. [HTTP Range Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
1. [HTTP Client Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints)
1. [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
1. [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
1. No third party dependencies
1. [Symlinks](https://en.wikipedia.org/wiki/Symbolic_link)
1. [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Allowing resources to be used on other domains can be crucial for providing APIs and services. Knowing how cumberstone and difficult is the process to setup the CORS, server ships with CORS enabled to all requests by default.
1. [HTTP Range Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests). Server supports requests for the part of the file, or several different parts of the file.
1. [HTTP Client Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints). Proactively asking client browser for suitable additional information about the system.
1. [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) set to nosniff, prevents from MIME type sniffing attacks.
1. [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options). Site is not allowed to be embedded into iframe on other domains.
1. [Symlinks](https://en.wikipedia.org/wiki/Symbolic_link). You can have symlinks in your folder and they will be resolved correctly.
1. [Caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#dealing_with_outdated_implementations) done right. It means no caching and therefore no outdated uncontrollable resources.
1. Resolving .html files without .html in path. It means if you try to open /some-html-file it will open file some-html-file.html and won't show 404 not found error. Same applies for folders. If you try to open /folder it will open file folder/index.html
1. Extensive logging. It means server prints the request-response pairs as they are so you can see all the details like request method, path, version and headers.
1. No third party dependencies.
1. [Forms](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) (without files)

## Documentation
Expand Down
44 changes: 43 additions & 1 deletion src/app/controller/static_resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,19 @@ impl Controller for StaticResourceController {
if boxed_md.is_ok() {
let md = boxed_md.unwrap();
if md.is_dir() {
return false
let mut directory_index = "index.html";

let last_char = components.path.chars().last().unwrap();
if last_char != '/' {
directory_index = "/index.html"
}
let index_html_in_directory_array = [&static_filepath, directory_index];
let index_html_in_directory = index_html_in_directory_array.join(SYMBOL.empty_string);

let boxed_file = File::open(&index_html_in_directory);
if boxed_file.is_err() {
return false
}
}
}

Expand Down Expand Up @@ -262,6 +274,36 @@ impl StaticResourceController {
let boxed_file = File::open(&static_filepath);
if boxed_file.is_ok() {
let md = metadata(&static_filepath).unwrap();
if md.is_dir() {
let mut range_header = &Header {
name: Header::_RANGE.to_string(),
value: "bytes=0-".to_string()
};

let boxed_header = request.get_header(Header::_RANGE.to_string());
if boxed_header.is_some() {
range_header = boxed_header.unwrap();
}

let mut directory_index = "index.html";

let last_char = components.path.chars().last().unwrap();
if last_char != '/' {
directory_index = "/index.html"
}

let url_array = [&components.path, directory_index];
let directory_index_html_path = url_array.join(SYMBOL.empty_string);

let boxed_content_range_list = Range::get_content_range_list(&directory_index_html_path, range_header);
if boxed_content_range_list.is_ok() {
content_range_list = boxed_content_range_list.unwrap();
} else {
let error = boxed_content_range_list.err().unwrap();
return Err(error)
}
}

if md.is_file() {
let mut range_header = &Header {
name: Header::_RANGE.to_string(),
Expand Down
132 changes: 132 additions & 0 deletions src/app/controller/static_resource/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,138 @@ use crate::request::{METHOD, Request};
use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
use crate::server::{Address, ConnectionInfo};

#[test]
fn directory_index_html() {
let path = "/static/";

let request = Request {
method: METHOD.get.to_string(),
request_uri: path.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers: vec![],
body: vec![],
};

let connection_info = ConnectionInfo {
client: Address { ip: "127.0.0.1".to_string(), port: 0 },
server: Address { ip: "127.0.0.1".to_string(), port: 0 },
request_size: 0,
};

let is_matching = StaticResourceController::is_matching(&request, &connection_info);
assert!(is_matching);

let mut response = Response::new();
response = StaticResourceController::process(&request, response, &connection_info);


let path_array = vec!["static", "index.html"];
let path = FileExt::build_path(&path_array);
let expected_text = FileExt::read_file(path.as_str()).unwrap();

let actual_text = response.content_range_list.get(0).unwrap().body.to_vec();
assert_eq!(actual_text, expected_text);
}

#[test]
fn directory_index_html_with_query() {
let path = "/static/?param=1234";

let request = Request {
method: METHOD.get.to_string(),
request_uri: path.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers: vec![],
body: vec![],
};

let connection_info = ConnectionInfo {
client: Address { ip: "127.0.0.1".to_string(), port: 0 },
server: Address { ip: "127.0.0.1".to_string(), port: 0 },
request_size: 0,
};

let is_matching = StaticResourceController::is_matching(&request, &connection_info);
assert!(is_matching);

let mut response = Response::new();
response = StaticResourceController::process(&request, response, &connection_info);


let path_array = vec!["static", "index.html"];
let path = FileExt::build_path(&path_array);
let expected_text = FileExt::read_file(path.as_str()).unwrap();

let actual_text = response.content_range_list.get(0).unwrap().body.to_vec();
assert_eq!(actual_text, expected_text);
}

#[test]
fn directory_index_html_no_slash() {
let path = "/static";

let request = Request {
method: METHOD.get.to_string(),
request_uri: path.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers: vec![],
body: vec![],
};

let connection_info = ConnectionInfo {
client: Address { ip: "127.0.0.1".to_string(), port: 0 },
server: Address { ip: "127.0.0.1".to_string(), port: 0 },
request_size: 0,
};

let is_matching = StaticResourceController::is_matching(&request, &connection_info);
assert!(is_matching);

let mut response = Response::new();
response = StaticResourceController::process(&request, response, &connection_info);


let path_array = vec!["static", "index.html"];
let path = FileExt::build_path(&path_array);
let expected_text = FileExt::read_file(path.as_str()).unwrap();

let actual_text = response.content_range_list.get(0).unwrap().body.to_vec();
assert_eq!(actual_text, expected_text);
}

#[test]
fn directory_index_html_no_slash_and_query() {
let path = "/static?param=1234";

let request = Request {
method: METHOD.get.to_string(),
request_uri: path.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers: vec![],
body: vec![],
};

let connection_info = ConnectionInfo {
client: Address { ip: "127.0.0.1".to_string(), port: 0 },
server: Address { ip: "127.0.0.1".to_string(), port: 0 },
request_size: 0,
};

let is_matching = StaticResourceController::is_matching(&request, &connection_info);
assert!(is_matching);

let mut response = Response::new();
response = StaticResourceController::process(&request, response, &connection_info);


let path_array = vec!["static", "index.html"];
let path = FileExt::build_path(&path_array);
let expected_text = FileExt::read_file(path.as_str()).unwrap();

let actual_text = response.content_range_list.get(0).unwrap().body.to_vec();
assert_eq!(actual_text, expected_text);
}

#[test]
fn file_retrieval() {
let path = "/static/test.txt";
Expand Down
12 changes: 11 additions & 1 deletion src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl Header {

pub const NAME_VALUE_SEPARATOR: &'static str = ": ";


pub const _DO_NOT_STORE_CACHE: &'static str = "no-store, no-cache, private, max-age=0, must-revalidate, proxy-revalidate";

pub fn get_header_list(request: &Request) -> Vec<Header> {
let mut header_list : Vec<Header>;
Expand Down Expand Up @@ -173,6 +173,9 @@ impl Header {
let date_iso_8601_header = Header::get_date_iso_8601_header();
header_list.push(date_iso_8601_header);

let no_cache = Header::get_no_cache_header();
header_list.push(no_cache);

header_list
}

Expand Down Expand Up @@ -205,6 +208,13 @@ impl Header {
}
}

pub fn get_no_cache_header() -> Header {
Header {
name: Header::_CACHE_CONTROL.to_string(),
value: Header::_DO_NOT_STORE_CACHE.to_string(),
}
}

pub fn parse_header(raw_header: &str) -> Result<Header, String> {
let escaped_header = StringExt::filter_ascii_control_characters(raw_header);
let escaped_header = StringExt::truncate_new_line_carriage_return(&escaped_header);
Expand Down
2 changes: 1 addition & 1 deletion src/log/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn log_request_response() {
let path = FileExt::build_path(&node_path);
let file_content = FileExt::read_file(&path).unwrap();
let timestamp = response._get_header(Header::_DATE_UNIX_EPOCH_NANOS.to_string()).unwrap();
let expected_log = format!("\n\nRequest (thread id: log::tests::log_request_response peer address is 0.0.0.0:0):\n HTTP/1.1 GET /script.js \n Host: 127.0.0.1:7878\n User-Agent: SOME USER AGENT\n Accept: */*\n Accept-Language: en-US,en;q=0.5\n Accept-Encoding: gzip, deflate, br\n Referer: https://127.0.0.1:7878/\n Body: 0 byte(s) total (including default initialization vector)\nEnd of Request\nResponse:\n 200 OK \n Accept-CH: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Downlink, ECT, RTT, Save-Data, Device-Memory, Sec-CH-Prefers-Reduced-Motion, Sec-CH-Prefers-Color-Scheme\n Critical-CH: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Downlink, ECT, RTT, Save-Data, Device-Memory, Sec-CH-Prefers-Reduced-Motion, Sec-CH-Prefers-Color-Scheme\n Vary: Origin, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Save-Data, Device-Memory, Upgrade-Insecure-Requests, Sec-CH-Prefers-Reduced-Motion, Sec-CH-Prefers-Color-Scheme\n X-Content-Type-Options: nosniff\n Accept-Ranges: bytes\n X-Frame-Options: SAMEORIGIN\n Date-Unix-Epoch-Nanos: {}\n\n Body: 1 part(s), {} byte(s) total\nEnd of Response", timestamp.value, file_content.len());
let expected_log = format!("\n\nRequest (thread id: log::tests::log_request_response peer address is 0.0.0.0:0):\n HTTP/1.1 GET /script.js \n Host: 127.0.0.1:7878\n User-Agent: SOME USER AGENT\n Accept: */*\n Accept-Language: en-US,en;q=0.5\n Accept-Encoding: gzip, deflate, br\n Referer: https://127.0.0.1:7878/\n Body: 0 byte(s) total (including default initialization vector)\nEnd of Request\nResponse:\n 200 OK \n Accept-CH: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Downlink, ECT, RTT, Save-Data, Device-Memory, Sec-CH-Prefers-Reduced-Motion, Sec-CH-Prefers-Color-Scheme\n Critical-CH: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Downlink, ECT, RTT, Save-Data, Device-Memory, Sec-CH-Prefers-Reduced-Motion, Sec-CH-Prefers-Color-Scheme\n Vary: Origin, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Save-Data, Device-Memory, Upgrade-Insecure-Requests, Sec-CH-Prefers-Reduced-Motion, Sec-CH-Prefers-Color-Scheme\n X-Content-Type-Options: nosniff\n Accept-Ranges: bytes\n X-Frame-Options: SAMEORIGIN\n Date-Unix-Epoch-Nanos: {}\n Cache-Control: no-store, no-cache, private, max-age=0, must-revalidate, proxy-revalidate\n\n Body: 1 part(s), {} byte(s) total\nEnd of Response", timestamp.value, file_content.len());

let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let log = Log::request_response(&request, &response, &peer_addr);
Expand Down
21 changes: 21 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File</title>
<link rel="stylesheet" href="/static/style.css">
<style>
label {
display: block;
}

label, button, h4, p {
margin: 2em 2em 0 2em;
}
</style>
</head>
<body>
Index html test

</body>
</html>

0 comments on commit d36db74

Please sign in to comment.