diff --git a/Cargo.toml b/Cargo.toml index 9840691..d6cc93b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -salvo = { version= "0.41" } -tokio = { version = "1", features = ["macros"] } +salvo = { version = "0.44", features = ["logging"] } +miette = { version = "5.9.0", features = ["fancy"]} +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } serde = { version = "1.0" } serde_json = "1.0" reqwest = "0.11.18" -once_cell = "1.17.1" \ No newline at end of file +once_cell = "1.17.1" +thiserror = "1.0.40" +tracing-subscriber = "0.3.17" +cfg-if = "1.0.0" + +[features] +# Defines a feature named `webp` that does not enable any other features. +v2 = [] \ No newline at end of file diff --git a/README.md b/README.md index 0b85417..c942c27 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,49 @@ bare-server-rust is a fully compliant Rust implementation of [TompHTTPs' Bare Server specifications](https://github.com/tomphttp/specifications/blob/master/BareServer.md). This is a server that will receive requests from a service worker (or any client) and forward a request to a specified URL. -# How to use +## Using +TODO: Release builds to docker, create simple install script. + +## Contributing +All support and contributions to `bare-server-rust` are appreciated. Including, but not limited to: documentation changes, bugfixes, feature requests, and performance improvements. + +### How do I get started? + +### Before we start +A quick note before we start, we use unsatable features for rustfmt, requiring the +nightly flag. Make sure you install a nightly toolchain as well. + +You can install the nightly rust toolchain with the following `rustup` command. +``` +rustup toolchain install nightly ``` -git clone https://github.com/NebulaServices/bare-server-rust -cd bare-server-rust +### Installing `rustup` +As with any rust project, you will need to install cargo, rust, and other dependencies. The easiest way to do this is with `rustup`. -cargo build && cargo run +If you are an a Unix based system, or intend to use Windows Subsystem for Linux to develop, you should run the `rustup` installer script below. ``` -## To-do -* Websocket support +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +Otherwise, please visit the [`Rustup Website`](https://rustup.rs/) to download it. +After you've finished with downloading rustup, be sure to install the nightly toolchain as shown [above](#before-we-start). +### Building from Sources +If you haven't done so already, you'll need to download the repository using git. +``` +git clone git@github.com:NebulaServices/bare-server-rust.git +``` +After you've download that, its time to go in and get to work. All you need to do is `cd` into the repository like so: +``` +cd bare-server-rust +``` +Afterwords, you can either build or run using the respective cargo commands. +``` +cargo run +cargo build +``` +As an example, to build the `release` profile with the `v2` feature enabled, you can do it like so: +``` +cargo run --features v2 --release +``` ## Authors * [UndefinedBHVR](https://github.com/UndefinedBHVR) diff --git a/rustfmt.toml b/rustfmt.toml index 0ebe0c8..7986781 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,7 @@ # NOTE: Wrapping comments required the nightly toolchain # A saddening amount of rustfmt features are locked on nightly # despite being more or less stable. +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +use_field_init_shorthand = true wrap_comments = true \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6c8c5fa --- /dev/null +++ b/src/error.rs @@ -0,0 +1,126 @@ +use miette::{Diagnostic, Report as StackReport}; +use reqwest::StatusCode; +use salvo::{ + async_trait, Depot, Request, Response, Writer, + __private::tracing::{self}, + writer::Json, +}; +use serde_json::{json, Value}; +use thiserror::Error; +pub type Result = core::result::Result; + +pub struct ErrorWithContext { + error: Error, + context: String, +} + +impl ErrorWithContext { + pub fn new>(error: Error, context: T) -> Self { + Self { + error, + context: context.into(), + } + } + + pub fn to_report(&self) -> miette::Report { + miette::Report::new(self.error.to_owned()).wrap_err(self.context.to_owned()) + } +} + +#[async_trait] +impl Writer for ErrorWithContext { + async fn write(mut self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) { + match self.error { + Error::Unknown + | Error::HostNotFound + | Error::ConnectionReset + | Error::ConnectionRefused + | Error::Generic(_) + | Error::ConnectionTimeout => res.status_code(StatusCode::INTERNAL_SERVER_ERROR), + Error::MissingBareHeader(_) + | Error::InvalidBareHeader(_) + | Error::UnknownBareHeader(_) + | Error::InvalidHeader(_) => res.status_code(StatusCode::BAD_REQUEST), + Error::ForbiddenBareHeader(_) => res.status_code(StatusCode::FORBIDDEN), + }; + let report = self.to_report(); + tracing::error!("\n {report:?}"); + res.render(Json(self.error.to_json())); + } +} + +#[derive(Debug, Diagnostic, Error, Clone)] +#[error("oops!")] +pub enum Error { + #[error("The Bare Server could not identify the cause of the issue")] + #[diagnostic(code(UNKNOWN))] + Unknown, + #[error("The request did not include the required header {0}")] + #[diagnostic(code(MISSING_BARE_HEADER))] + MissingBareHeader(String), + #[error("Received an unrecognizable header value: {0}")] + #[diagnostic(code(INVALID_BARE_HEADER))] + InvalidBareHeader(String), + #[error("Received a forbidden header value: {0}")] + #[diagnostic(code(FORBIDDEN_BARE_HEADER))] + ForbiddenBareHeader(String), + // NOTE: This is unused, checking for unknown headers is a waste of compute. + // I may gate this behind a feature flag at a later date. + #[error("Received unknown bare header {0}")] + #[diagnostic(code(UNKNOWN_BARE_HEADER))] + UnknownBareHeader(String), + // Why does this exist? This is a duplicate of InvalidBareHeader... + #[error("Received a blacklisted header value: {0}")] + #[diagnostic(code(INVALID_HEADER))] + InvalidHeader(String), + #[error("The DNS lookup for the host failed.")] + #[diagnostic(code(HOST_NOT_FOUND))] + HostNotFound, + #[error("The connection to the remote was closed early.")] + #[diagnostic(code(CONNECTION_RESET))] + ConnectionReset, + #[error("The connection to the remote was refused.")] + #[diagnostic(code(CONNECTION_REFUSED))] + ConnectionRefused, + #[error("The remote didn't respond with headers/body in time.")] + #[diagnostic(code(CONNECTION_TIMEOUT))] + ConnectionTimeout, + #[error("{0}")] + #[diagnostic(code(UNKNOWN))] + Generic(String), +} + +impl Error { + pub fn to_json(&self) -> Value { + let id: String = match self { + Error::Unknown => "unknown".into(), + Error::MissingBareHeader(header) | Error::InvalidBareHeader(header) => { + format!("request.headers.{}", header.to_lowercase()) + } + Error::ForbiddenBareHeader(header) => format!("error.temp.forbidden_header.{header}"), + Error::UnknownBareHeader(header) => format!("error.temp.unknown_bare_header.{header}"), + Error::InvalidHeader(header) => format!("error.temp.invalid_header.{header}"), + Error::HostNotFound => "error.http.not_found".to_string(), + Error::ConnectionReset => "error.http.reset".to_string(), + Error::ConnectionRefused => "error.http.refused".to_string(), + Error::ConnectionTimeout => "error.http.timeout".to_string(), + Error::Generic(kind) => format!("request.tomphttp-rs.{kind}"), + }; + + json!({ + "code": format!("{}", self.code().expect("This should always be defined.")), + "id": id, + "message": format!("{self}") + + }) + } +} + +#[async_trait] +impl Writer for Error { + async fn write(mut self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) { + let report: StackReport = self.into(); + tracing::error!("\n {report:?}"); + res.render(format!("{report:?}")); + } +} diff --git a/src/main.rs b/src/main.rs index fe7264c..d585f82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,30 @@ +use error::{Error, ErrorWithContext}; use reqwest::Client; -use salvo::prelude::*; +use salvo::{__private::tracing, prelude::*}; use util::REQWEST_CLIENT; +use v3::add_cors_headers_route; +//use util::REQWEST_CLIENT; use version::VersionData; +#[macro_use] +extern crate cfg_if; +mod v3; -pub mod routes; +pub mod error; pub mod util; pub mod version; #[handler] -async fn versions(res: &mut Response) { - res.render(Json(VersionData::default())); +async fn versions() -> Json { + Json(VersionData::default()) +} + +#[handler] +async fn error_test() -> Result<(), ErrorWithContext> { + let report = Err(ErrorWithContext::new( + Error::Unknown, + "While testing errors.", + )); + report? } #[tokio::main] @@ -17,7 +32,30 @@ async fn main() { REQWEST_CLIENT .set(Client::new()) .expect("This should never error"); + tracing_subscriber::fmt::init(); - let acceptor = TcpListener::new("127.0.0.1:5800").bind().await; - Server::new(acceptor).serve(routes::built_routes()).await; + // Compiler will complain. + #[allow(unused_mut)] + let mut app = Router::new() + //.hoop(Logger::new()) + .hoop(add_cors_headers_route) + .get(versions) + .push( + Router::with_path("v3") + .hoop(crate::v3::process_headers) + .handle(crate::v3::fetch), + ) + .push(Router::with_path("error").get(error_test)); + cfg_if! { + if #[cfg(feature = "v2")] { + tracing::info!("server configured to run with the `v2` feature enabled."); + app = app.push( + Router::with_path("v2") + .hoop(crate::v3::process_headers) + .handle(crate::v3::fetch), + ); + } + } + let server = TcpListener::new("127.0.0.1:5800").bind().await; + Server::new(server).serve(app).await; } diff --git a/src/routes.rs b/src/routes.rs deleted file mode 100644 index 37da27f..0000000 --- a/src/routes.rs +++ /dev/null @@ -1,181 +0,0 @@ -use reqwest::header::HeaderMap; - -use salvo::http::header::{HeaderName, HeaderValue}; - -use salvo::http::request::secure_max_size; -use salvo::prelude::*; - - -use crate::util::{join_bare_headers, split_headers, ProcessedHeaders, REQWEST_CLIENT}; -use crate::version::VersionData; -use std::any::TypeId; -use std::collections::HashMap; -use std::ops::DerefMut; -use std::str::FromStr; - -#[handler] -async fn versions(res: &mut Response) { - add_cors_headers(res); - res.render(Json(VersionData::default())); -} - -#[handler] -/// A function to preprocess headers from a request and inject them to the depot -async fn preprocess_headers(req: &mut Request, depot: &mut Depot) { - // Get a mutable reference to the headers from the request - let headers: &mut HeaderMap = req.headers_mut(); - // Create a new processed headers object with default values - let mut processed = ProcessedHeaders::default(); - - // Process forwarded headers using functional methods - // Get the value of x-bare-forward-headers or use an empty string as default - let header_value = headers.get("x-bare-forward-headers").map_or("", |h| { - h.to_str().expect("Should map to string successfully") - }); - // Split the value by comma and space and collect it into a vector - let forwarded_heads: Vec = header_value.split(", ").map(|s| s.to_owned()).collect(); - // Filter out the invalid headers and append the valid ones to the processed - // headers - forwarded_heads - .iter() - .filter(|head| { - match head.as_str() { - // If headers are invalid, we don't need them. - "connection" | "transfer-encoding" | "host" | "origin" | "referer" | "" => false, - _ => true, - } - }) - .for_each(|head| { - println!("Current head: {head}"); - let he = &HeaderName::from_str(head).expect("Should not fail here"); - processed.append(he, headers.get(he).expect("Header should exist").clone()); - }); - - // Get the value of x-bare-headers or use the joined bare headers as default - let bare_headers = headers - .get("x-bare-headers") - .map_or_else(|| join_bare_headers(headers).unwrap(), |h| h.to_owned()); - - // Process bare headers if they exist - if !bare_headers.is_empty() { - // Deserialize the bare headers into a hashmap of strings - let data: HashMap = - serde_json::from_str(bare_headers.to_str().expect("Should be valid string")) - .expect("Should not fail to Str:Str deserialize"); - // Append the hashmap entries to the processed headers - data.iter().for_each(|(head, value)| { - processed.append( - HeaderName::from_str(head).unwrap(), - HeaderValue::from_str(value).unwrap(), - ); - }); - - // Pass content length header too. - if let Some(content_length) = headers.get("content-length") { - processed.append( - HeaderName::from_str("content-length").unwrap(), - content_length.to_owned(), - ); - } - // Host key is not needed, I think? - processed.remove("host"); - } - // Inject processed headers to the depot. - depot.inject(processed); -} - -#[handler] -/// Handler for [`TOMPHttp V2`](https://github.com/tomphttp/specifications/blob/master/BareServerV2.md#send-and-receive-data-from-a-remote) requests. -async fn v2_get(req: &mut Request, res: &mut Response, depot: &mut Depot) { - // Get a mutable reference to the processed headers from the depot - let headers: &mut ProcessedHeaders = depot - .get_mut(&format!("{:?}", TypeId::of::())) - .unwrap(); - - // Get the path from the request header or use "/" as default - let path = req - .header::("x-bare-path") - .unwrap_or("/".to_owned()); - - // Construct the full URL from the request header or use default values - let url = format!( - "{}//{}{}", - // Assume HTTPS if not specified - req.header::("x-bare-protocol") - .unwrap_or("https:".to_owned()), - req.header::("x-bare-host") - .unwrap_or("example.com".to_owned()), - path - ); - - // Make a new request using the same method and URL as the original one - let response = REQWEST_CLIENT - .get() - .unwrap() - .request(req.method().clone(), url) - // Use the processed headers as the new request headers - .headers(headers.deref_mut().to_owned()) - // Read the payload from the original request with a maximum size limit - .body( - req.payload_with_max_size(secure_max_size()) - .await - .expect("Probably won't error?") - .to_vec(), - ) - // Send the new request and panic if it fails - .send() - .await - .unwrap_or_else(|x| { - panic!("{x}"); - }); - - // Set the status code of the response to match the new request's status code - res.status_code(response.status()); - // Set x-bare-headers to show the new request's headers - res.add_header("x-bare-headers", format!("{:?}", response.headers()), true) - .expect("This shouldn't fail, probably?"); - // Split the headers if needed - res.set_headers(split_headers(&res.headers)); - // Set some of the required headers from the new request's headers - if let Some(header) = response.headers().get("content-type") { - res.add_header("content-type", header, true).unwrap(); - } - res.add_header("x-bare-status", response.status().as_str(), true) - .expect("This shouldn't fail."); - res.add_header( - "x-bare-status-text", - response.status().canonical_reason().expect("Should exist"), - true, - ) - .expect("This shouldn't fail"); - // Add cors headers to the response - add_cors_headers(res); - - // Write the body of the response using the bytes from the new request's - // response - res.write_body(response.bytes().await.unwrap()) - .expect("This should not fail?"); -} - -/// Blanket fix for CORS headers while in dev. -/// -/// THIS IS BAD AND A SEC VULN, WILL BE FIXED LATER. -fn add_cors_headers(res: &mut Response) { - res.add_header("access-control-allow-origin", "*", true) - .unwrap(); - res.add_header("access-control-allow-headers", "*", true) - .unwrap(); - res.add_header("access-control-allow-methods", "*", true) - .unwrap(); - res.add_header("access-control-expose-headers", "*", true) - .unwrap(); -} - -/// Build our routes. -pub fn built_routes() -> Router { - Router::new().get(versions).push( - Router::with_path("v2") - .hoop(preprocess_headers) - .handle(v2_get), - ) -} diff --git a/src/util.rs b/src/util.rs index 7ce2c08..774cda8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,15 +1,42 @@ +use std::{ + ops::{Deref, DerefMut}, + str::{self, FromStr}, +}; + use once_cell::sync::OnceCell; use reqwest::Client; use salvo::{ http::HeaderValue, hyper::{http::HeaderName, HeaderMap}, }; -use std::{ - ops::{Deref, DerefMut}, - str::{self, FromStr}, -}; + +use crate::error::{Error, ErrorWithContext}; const MAX_HEADER_VALUE: usize = 3072; pub static REQWEST_CLIENT: OnceCell = OnceCell::new(); + +#[derive(Default, Clone, Debug)] +pub struct RequestData { + pub processed_headers: HeaderMap, + pub pass_headers: Option>, + pub status: Option>, +} + +impl RequestData { + pub fn explode_ref_mut( + &mut self, + ) -> ( + &mut HeaderMap, + Option<&mut Vec>, + Option<&mut Vec>, + ) { + ( + &mut self.processed_headers, + self.pass_headers.as_mut(), + self.status.as_mut(), + ) + } +} + #[derive(Default, Clone, Debug)] pub struct ProcessedHeaders(HeaderMap); @@ -82,35 +109,35 @@ pub fn split_headers(headers: &HeaderMap) -> HeaderMap { /// /// The original case of the header names is preserved and other headers are not /// modified. -pub fn join_bare_headers(headers: &HeaderMap) -> Result { - let mut err: Option = None; +pub fn join_bare_headers(headers: &HeaderMap) -> Result { + // Create an early out if `x-bare-headers` exists on its own + if let Some(header) = headers.get("x-bare-headers") { + return Ok(header.to_owned()); + } // Create a new empty string for the joined header value let mut joined_value = String::new(); - // Iterate over each header in the input header map - headers.iter().for_each(|(name, value)| { - // Check if the header name has the prefix "x-bare-headers-" (case-insensitive) - if name.as_str().to_lowercase().starts_with("x-bare-headers-") { - if !value + // Why couldn't they have used duplicate headers. + // It'd be less ugly. Oh well. + let mut x = 0; + while let Some(header) = headers.get(format!("x-bare-headers-{x}")) { + joined_value.push_str( + header .to_str() - .expect("[Join Headers] Should be convertable to string") - .starts_with(';') - { - err = Some("Header started with invalid character.".into()); - } - // Append the header value to the joined value string - joined_value.push_str( - value - .to_str() - .expect("[Join Headers] Failed to convert header value to string?"), - ); - } - }); - if let Some(e) = err { - return Err(e); + .expect("[Join Headers] Failed to convert header value to string?"), + ); + x += 1; } + + // We can assume that this header was never specified.. + if joined_value.is_empty() && x != 0 { + Err(ErrorWithContext::new( + Error::MissingBareHeader("X-BARE-HEADERS".into()), + "While joining headers", + ))? + } + // Create a new header value from the joined value string - let joined_value = HeaderValue::from_str(&joined_value) - .expect("[Join Headers] Failed to create header value?"); + let joined_value = HeaderValue::from_str(&joined_value).unwrap(); // Return joined values Ok(joined_value) } diff --git a/src/v3.rs b/src/v3.rs new file mode 100644 index 0000000..4e92997 --- /dev/null +++ b/src/v3.rs @@ -0,0 +1,183 @@ +use std::{any::TypeId, collections::HashMap, str::FromStr}; + +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use salvo::{http::request::secure_max_size, prelude::*}; + +use crate::{ + error::{self, Error, ErrorWithContext}, + util::{self, split_headers, RequestData, REQWEST_CLIENT}, +}; + +#[handler] +pub async fn process_headers(req: &mut Request, depot: &mut Depot) -> error::Result<()> { + let headers: &mut HeaderMap = req.headers_mut(); + let bare_headers = util::join_bare_headers(headers)?; + let mut processed = HeaderMap::new(); + headers + .get("X-BARE-FORWARD-HEADERS") + .get_or_insert(&HeaderValue::from_str("").unwrap()) + .to_str() + .expect("Should not fail") + .split(", ") + .collect::>() + .iter() + .filter(|head| match **head { + // If headers are invalid, we don't need them. + "connection" | "transfer-encoding" | "host" | "origin" | "referer" | "" => false, + _ => true, + }) + .for_each(|head| { + let he = &HeaderName::from_str(head).expect("Should not fail here"); + processed.append(he, headers.get(he).expect("Header should exist").clone()); + }); + + let bare_header_map: HashMap = + serde_json::from_str(bare_headers.to_str().expect("Should be valid string")) + .unwrap_or(HashMap::new()); + + // Append the hashmap entries to the processed headers + bare_header_map.iter().for_each(|(head, value)| { + processed.append( + HeaderName::from_str(head).unwrap(), + HeaderValue::from_str(value).unwrap(), + ); + }); + + // Pass content length header too. + if let Some(content_length) = headers.get("content-length") { + processed.append( + HeaderName::from_str("content-length").unwrap(), + content_length.to_owned(), + ); + } + + // We don't need the host key, can cause issues if specified improperly. + processed.remove("host"); + + depot.inject(RequestData { + processed_headers: processed, + pass_headers: None, + status: None, + }); + Ok(()) +} + +#[handler] +pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> error::Result<()> { + let (headers, pass_headers, statuses) = depot + .get_mut::(&format!("{:?}", TypeId::of::())) + .unwrap() + .explode_ref_mut(); + + cfg_if! { + if #[cfg(feature = "v2")] { + let url = req.header::("x-bare-url").unwrap_or(format!( + "{}//{}{}", + // Assume HTTPS if not specified + req.header::("x-bare-protocol") + .unwrap_or("https:".to_owned()), + req.header::("x-bare-host") + .unwrap_or("example.com".to_owned()), + req.header::("x-bare-path") + .unwrap_or("/".to_owned()) + )); + } else { + let url = req.header::("x-bare-url") + .ok_or(ErrorWithContext::new(Error::MissingBareHeader("x-bare-url".into()), "While processing v3 request."))?; + } + } + + let response = REQWEST_CLIENT + .get() + .unwrap() + .request(req.method().clone(), url) + // Use the processed headers as the new request headers + .headers(headers.to_owned()) + // Read the payload from the original request with a maximum size limit + .body( + req.payload_with_max_size(secure_max_size()) + .await + .map_err(|_| { + ErrorWithContext::new( + Error::Generic("invalid_body".into()), + "Couldn't parse request body.", + ) + })? + .to_vec(), + ) + .send() + .await + .map_err(|e| { + ErrorWithContext::new(Error::Generic("unhandled_error".into()), e.to_string()) + })?; + + resp.add_header("x-bare-headers", format!("{:?}", response.headers()), true) + .expect("This shouldn't fail, probably?"); + // Split the headers if needed + resp.set_headers(split_headers(&resp.headers)); + + if statuses.is_some() && statuses.unwrap().contains(&response.status().to_string()) { + resp.status_code(response.status()); + } + + if pass_headers.is_some() { + pass_headers.unwrap().iter().for_each(|header| { + if let Some(value) = response.headers().get(header) { + resp.headers + .append(HeaderName::from_str(header).unwrap(), value.clone()); + } + }); + } + + // We should ALWAYS copy the content type. + if let Some(header) = response.headers().get("content-type") { + resp.add_header("content-type", header, true).unwrap(); + } + resp.add_header("x-bare-status", response.status().as_str(), true) + .expect("Should never fail to add `x-bare-status`"); + + resp.add_header( + "x-bare-status-text", + response + .status() + .canonical_reason() + .expect("canonical_reason should always exist."), + true, + ) + .expect("Should never fail to add `x-bare-status-text`"); + + add_cors_headers(resp); + + resp.write_body(response.bytes().await.unwrap()) + .expect("This should not fail?"); + Ok(()) +} + +/// Blanket fix for CORS headers while in dev. +/// +/// THIS IS BAD AND A SEC VULN, WILL BE FIXED LATER. +fn add_cors_headers(res: &mut Response) { + res.add_header("access-control-allow-origin", "*", true) + .unwrap(); + res.add_header("access-control-allow-headers", "*", true) + .unwrap(); + res.add_header("access-control-allow-methods", "*", true) + .unwrap(); + res.add_header("access-control-expose-headers", "*", true) + .unwrap(); +} + +/// Blanket fix for CORS headers while in dev. +/// +/// THIS IS BAD AND A SEC VULN, WILL BE FIXED LATER. +#[handler] +pub fn add_cors_headers_route(res: &mut Response) { + res.add_header("access-control-allow-origin", "*", true) + .unwrap(); + res.add_header("access-control-allow-headers", "*", true) + .unwrap(); + res.add_header("access-control-allow-methods", "*", true) + .unwrap(); + res.add_header("access-control-expose-headers", "*", true) + .unwrap(); +} diff --git a/src/version.rs b/src/version.rs index eeea443..7412500 100644 --- a/src/version.rs +++ b/src/version.rs @@ -23,9 +23,16 @@ pub struct VersionData { // Implement the Default trait for VersionData to provide a default value impl Default for VersionData { fn default() -> Self { + let mut versions = Vec::new(); + versions.push("v3".into()); + cfg_if! { + if #[cfg(feature = "v2")] { + versions.push("v2".into()); + } + } Self { - // Currently the project supports only version 2 of the [`TOMPHTTP`](https://github.com/tomphttp/specifications/blob/master/BareServerV2.md) specification - versions: vec!["v2".into()], + // Currently the project supports only version 3 of the [`TOMPHTTP`](https://github.com/tomphttp/specifications/blob/master/BareServerV2.md) specification + versions, // The project is written in Rust language: "Rust".into(), // Use the default value for MaintainerData @@ -76,8 +83,9 @@ pub struct ProjectData { impl Default for ProjectData { fn default() -> Self { Self { - name: "Nebula TOMPHTTP Server".into(), - description: "Clean implementation of the TOMPHttp Specification in Rust.".into(), + name: "tomphttp-rs server".into(), + description: + "Clean implementation of the tomphttp specificiation in the Rust language.".into(), email: "".into(), website: "https://github.com/NebulaServices/bare-server-rust".into(), repository: "https://github.com/NebulaServices/bare-server-rust".into(),