From c3a74ba8af2a226d7cd67e64bfe7e42c56a884a0 Mon Sep 17 00:00:00 2001 From: UndefinedBHVR Date: Sun, 9 Jul 2023 19:17:00 -0500 Subject: [PATCH 1/6] Begin work on V3 --- Cargo.toml | 9 ++-- src/error.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 34 ++++++++++---- src/routes.rs | 9 ++-- src/util.rs | 39 +++++++-------- src/v3.rs | 22 +++++++++ src/version.rs | 5 +- 7 files changed, 200 insertions(+), 43 deletions(-) create mode 100644 src/error.rs create mode 100644 src/v3.rs diff --git a/Cargo.toml b/Cargo.toml index 9840691..d862c30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,12 @@ 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" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..33e7c4a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,125 @@ +use std::fmt::Display; + +use miette::{Diagnostic, Report as StackReport}; +use reqwest::StatusCode; +use salvo::{ + async_trait, Depot, Request, Response, Writer, + __private::tracing::{self, instrument::WithSubscriber}, + writer::Json, +}; +use serde::{Deserialize, Serialize}; +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::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, +} + +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(_) => todo!(), + Error::UnknownBareHeader(_) => todo!(), + Error::InvalidHeader(_) => todo!(), + Error::HostNotFound => todo!(), + Error::ConnectionReset => todo!(), + Error::ConnectionRefused => todo!(), + Error::ConnectionTimeout => todo!(), + }; + + 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.code().unwrap()); + tracing::error!("\n {report:?}"); + res.render(format!("{report:?}")); + } +} diff --git a/src/main.rs b/src/main.rs index fe7264c..7debf60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,37 @@ +use error::{Error, ErrorWithContext}; +use miette::{Context, Diagnostic, ErrReport as Report, IntoDiagnostic}; use reqwest::Client; -use salvo::prelude::*; -use util::REQWEST_CLIENT; +use salvo::{__private::tracing, prelude::*}; +//use util::REQWEST_CLIENT; use version::VersionData; +mod v3; +pub mod error; pub mod routes; 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] async fn main() { - REQWEST_CLIENT - .set(Client::new()) - .expect("This should never error"); - - let acceptor = TcpListener::new("127.0.0.1:5800").bind().await; - Server::new(acceptor).serve(routes::built_routes()).await; + tracing_subscriber::fmt::init(); + let app = Router::new() + .hoop(Logger::new()) + .get(versions) + .push(Router::with_path("error").get(error_test)); + 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 index 37da27f..7c9d421 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -3,9 +3,9 @@ use reqwest::header::HeaderMap; use salvo::http::header::{HeaderName, HeaderValue}; use salvo::http::request::secure_max_size; +use salvo::logging::Logger; use salvo::prelude::*; - use crate::util::{join_bare_headers, split_headers, ProcessedHeaders, REQWEST_CLIENT}; use crate::version::VersionData; use std::any::TypeId; @@ -52,9 +52,7 @@ async fn preprocess_headers(req: &mut Request, depot: &mut Depot) { }); // 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()); + let bare_headers = join_bare_headers(headers); // Process bare headers if they exist if !bare_headers.is_empty() { @@ -173,7 +171,8 @@ fn add_cors_headers(res: &mut Response) { /// Build our routes. pub fn built_routes() -> Router { - Router::new().get(versions).push( + tracing_subscriber::fmt().init(); + Router::new().hoop(Logger::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..4616040 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,6 +8,7 @@ use std::{ ops::{Deref, DerefMut}, str::{self, FromStr}, }; +use tracing_subscriber::fmt::format; const MAX_HEADER_VALUE: usize = 3072; pub static REQWEST_CLIENT: OnceCell = OnceCell::new(); #[derive(Default, Clone, Debug)] @@ -82,35 +83,27 @@ 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) -> HeaderValue { + // Create an early out if `x-bare-headers` exists on its own + if let Some(header) = headers.get("x-bare-headers") { + return 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 = 1; + 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; } // 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?"); // Return joined values - Ok(joined_value) + joined_value } diff --git a/src/v3.rs b/src/v3.rs new file mode 100644 index 0000000..3f8dfe9 --- /dev/null +++ b/src/v3.rs @@ -0,0 +1,22 @@ +use std::any::Any; + +use crate::{error, util}; +use reqwest::header::HeaderMap; +use salvo::prelude::*; + +pub struct ProcessedData {} + +#[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 forward: Option = None; + if let Some(forward_headers) = headers.get("X-BARE-FORWARD-HEADERS") {} + + todo!() +} + +#[handler] +pub async fn fetch() -> &'static str { + todo!() +} diff --git a/src/version.rs b/src/version.rs index eeea443..913a10d 100644 --- a/src/version.rs +++ b/src/version.rs @@ -76,8 +76,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(), From 97225e87327bef2ec86c6fae1be2a3cf66c876d7 Mon Sep 17 00:00:00 2001 From: UndefinedBHVR Date: Sun, 9 Jul 2023 23:23:44 -0500 Subject: [PATCH 2/6] Running now, kind of. --- rustfmt.toml | 3 + src/error.rs | 15 +++-- src/main.rs | 14 +++- src/routes.rs | 32 ++++----- src/util.rs | 46 +++++++++---- src/v3.rs | 172 +++++++++++++++++++++++++++++++++++++++++++++---- src/version.rs | 4 +- 7 files changed, 237 insertions(+), 49 deletions(-) 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 index 33e7c4a..618944b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,10 @@ -use std::fmt::Display; - use miette::{Diagnostic, Report as StackReport}; use reqwest::StatusCode; use salvo::{ async_trait, Depot, Request, Response, Writer, - __private::tracing::{self, instrument::WithSubscriber}, + __private::tracing::{self}, writer::Json, }; -use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use thiserror::Error; pub type Result = core::result::Result; @@ -32,12 +29,13 @@ impl ErrorWithContext { #[async_trait] impl Writer for ErrorWithContext { - async fn write(mut self, req: &mut Request, depot: &mut Depot, res: &mut Response) { + 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(_) @@ -87,6 +85,9 @@ pub enum Error { #[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 { @@ -103,6 +104,7 @@ impl Error { Error::ConnectionReset => todo!(), Error::ConnectionRefused => todo!(), Error::ConnectionTimeout => todo!(), + Error::Generic(_) => todo!(), }; json!({ @@ -116,9 +118,8 @@ impl Error { #[async_trait] impl Writer for Error { - async fn write(mut self, req: &mut Request, depot: &mut Depot, res: &mut Response) { + async fn write(mut self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) { let report: StackReport = self.into(); - tracing::error!("\n{}", report.code().unwrap()); tracing::error!("\n {report:?}"); res.render(format!("{report:?}")); } diff --git a/src/main.rs b/src/main.rs index 7debf60..5e2f59e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use error::{Error, ErrorWithContext}; -use miette::{Context, Diagnostic, ErrReport as Report, IntoDiagnostic}; use reqwest::Client; -use salvo::{__private::tracing, prelude::*}; +use salvo::{prelude::*, __private::tracing}; +use util::REQWEST_CLIENT; +use v3::add_cors_headers_route; //use util::REQWEST_CLIENT; use version::VersionData; mod v3; @@ -27,10 +28,17 @@ async fn error_test() -> Result<(), ErrorWithContext> { #[tokio::main] async fn main() { + REQWEST_CLIENT + .set(Client::new()) + .expect("This should never error"); tracing_subscriber::fmt::init(); + tracing::info!("We gucchi"); let app = Router::new() - .hoop(Logger::new()) + //.hoop(Logger::new()) + .hoop(add_cors_headers_route) .get(versions) + .push(Router::with_path("v2").hoop(crate::v3::process_headers).handle(crate::v3::fetch)) + .push(Router::with_path("v3").hoop(crate::v3::process_headers).handle(crate::v3::fetch)) .push(Router::with_path("error").get(error_test)); 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 index 7c9d421..7b2de02 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,17 +1,19 @@ -use reqwest::header::HeaderMap; - -use salvo::http::header::{HeaderName, HeaderValue}; +use std::{any::TypeId, collections::HashMap, ops::DerefMut, str::FromStr}; -use salvo::http::request::secure_max_size; -use salvo::logging::Logger; -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; +use reqwest::header::HeaderMap; +use salvo::{ + http::{ + header::{HeaderName, HeaderValue}, + request::secure_max_size, + }, + logging::Logger, + prelude::*, +}; + +use crate::{ + util::{join_bare_headers, split_headers, ProcessedHeaders, REQWEST_CLIENT}, + version::VersionData, +}; #[handler] async fn versions(res: &mut Response) { @@ -55,7 +57,7 @@ async fn preprocess_headers(req: &mut Request, depot: &mut Depot) { let bare_headers = join_bare_headers(headers); // Process bare headers if they exist - if !bare_headers.is_empty() { + if let Ok(bare_headers) = bare_headers { // 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")) @@ -84,7 +86,7 @@ async fn preprocess_headers(req: &mut Request, depot: &mut Depot) { #[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) { +pub(crate) 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::())) diff --git a/src/util.rs b/src/util.rs index 4616040..8ca1663 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,16 +1,32 @@ +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 tracing_subscriber::fmt::format; + +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); @@ -83,16 +99,16 @@ 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) -> HeaderValue { +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 header.to_owned(); + return Ok(header.to_owned()); } // Create a new empty string for the joined header value let mut joined_value = String::new(); // Why couldn't they have used duplicate headers. // It'd be less ugly. Oh well. - let mut x = 1; + let mut x = 0; while let Some(header) = headers.get(format!("x-bare-headers-{x}")) { joined_value.push_str( header @@ -101,9 +117,17 @@ pub fn join_bare_headers(headers: &HeaderMap) -> HeaderValue { ); 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 - joined_value + Ok(joined_value) } diff --git a/src/v3.rs b/src/v3.rs index 3f8dfe9..d856cd7 100644 --- a/src/v3.rs +++ b/src/v3.rs @@ -1,22 +1,172 @@ -use std::any::Any; +use std::{collections::HashMap, str::FromStr, any::TypeId, ops::DerefMut}; -use crate::{error, util}; -use reqwest::header::HeaderMap; -use salvo::prelude::*; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use salvo::{prelude::*, http::request::secure_max_size}; -pub struct ProcessedData {} +use crate::{ + error::{self, Error, ErrorWithContext}, + util::{self, ProcessedHeaders, REQWEST_CLIENT, RequestData, split_headers}, +}; #[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 forward: Option = None; - if let Some(forward_headers) = headers.get("X-BARE-FORWARD-HEADERS") {} + 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()); + }); - todo!() + 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.into(), + pass_headers: None, + status: None + }); + Ok(()) } #[handler] -pub async fn fetch() -> &'static str { - todo!() +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(); + + 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()) + )); + + 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 the new request and panic if it fails + .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(); +} \ No newline at end of file diff --git a/src/version.rs b/src/version.rs index 913a10d..b24d3d8 100644 --- a/src/version.rs +++ b/src/version.rs @@ -24,8 +24,8 @@ pub struct VersionData { impl Default for VersionData { fn default() -> Self { 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: vec!["v3".into(), "v2".into()], // The project is written in Rust language: "Rust".into(), // Use the default value for MaintainerData From 0ba54de57727b266b424014cd3caca92a58bfc08 Mon Sep 17 00:00:00 2001 From: UndefinedBHVR Date: Sun, 9 Jul 2023 23:26:53 -0500 Subject: [PATCH 3/6] Remove dead code. Format code. --- src/main.rs | 15 +++-- src/routes.rs | 182 -------------------------------------------------- src/util.rs | 18 +++-- src/v3.rs | 61 +++++++++-------- 4 files changed, 58 insertions(+), 218 deletions(-) delete mode 100644 src/routes.rs diff --git a/src/main.rs b/src/main.rs index 5e2f59e..8c5919f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use error::{Error, ErrorWithContext}; use reqwest::Client; -use salvo::{prelude::*, __private::tracing}; +use salvo::{__private::tracing, prelude::*}; use util::REQWEST_CLIENT; use v3::add_cors_headers_route; //use util::REQWEST_CLIENT; @@ -8,7 +8,6 @@ use version::VersionData; mod v3; pub mod error; -pub mod routes; pub mod util; pub mod version; @@ -37,8 +36,16 @@ async fn main() { //.hoop(Logger::new()) .hoop(add_cors_headers_route) .get(versions) - .push(Router::with_path("v2").hoop(crate::v3::process_headers).handle(crate::v3::fetch)) - .push(Router::with_path("v3").hoop(crate::v3::process_headers).handle(crate::v3::fetch)) + .push( + Router::with_path("v2") + .hoop(crate::v3::process_headers) + .handle(crate::v3::fetch), + ) + .push( + Router::with_path("v3") + .hoop(crate::v3::process_headers) + .handle(crate::v3::fetch), + ) .push(Router::with_path("error").get(error_test)); 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 7b2de02..0000000 --- a/src/routes.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::{any::TypeId, collections::HashMap, ops::DerefMut, str::FromStr}; - -use reqwest::header::HeaderMap; -use salvo::{ - http::{ - header::{HeaderName, HeaderValue}, - request::secure_max_size, - }, - logging::Logger, - prelude::*, -}; - -use crate::{ - util::{join_bare_headers, split_headers, ProcessedHeaders, REQWEST_CLIENT}, - version::VersionData, -}; - -#[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 = join_bare_headers(headers); - - // Process bare headers if they exist - if let Ok(bare_headers) = bare_headers { - // 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. -pub(crate) 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 { - tracing_subscriber::fmt().init(); - Router::new().hoop(Logger::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 8ca1663..774cda8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -15,15 +15,25 @@ const MAX_HEADER_VALUE: usize = 3072; pub static REQWEST_CLIENT: OnceCell = OnceCell::new(); #[derive(Default, Clone, Debug)] -pub struct RequestData{ +pub struct RequestData { pub processed_headers: HeaderMap, pub pass_headers: Option>, - pub status: 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()) + 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(), + ) } } diff --git a/src/v3.rs b/src/v3.rs index d856cd7..29072c1 100644 --- a/src/v3.rs +++ b/src/v3.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap, str::FromStr, any::TypeId, ops::DerefMut}; +use std::{any::TypeId, collections::HashMap, str::FromStr}; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -use salvo::{prelude::*, http::request::secure_max_size}; +use salvo::{http::request::secure_max_size, prelude::*}; use crate::{ error::{self, Error, ErrorWithContext}, - util::{self, ProcessedHeaders, REQWEST_CLIENT, RequestData, split_headers}, + util::{self, split_headers, RequestData, REQWEST_CLIENT}, }; #[handler] @@ -15,9 +15,7 @@ pub async fn process_headers(req: &mut Request, depot: &mut Depot) -> error::Res let mut processed = HeaderMap::new(); headers .get("X-BARE-FORWARD-HEADERS") - .get_or_insert( - &HeaderValue::from_str("").unwrap() - ) + .get_or_insert(&HeaderValue::from_str("").unwrap()) .to_str() .expect("Should not fail") .split(", ") @@ -34,7 +32,8 @@ pub async fn process_headers(req: &mut Request, depot: &mut Depot) -> error::Res }); let bare_header_map: HashMap = - serde_json::from_str(bare_headers.to_str().expect("Should be valid string")).unwrap_or(HashMap::new()); + 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)| { @@ -56,9 +55,9 @@ pub async fn process_headers(req: &mut Request, depot: &mut Depot) -> error::Res processed.remove("host"); depot.inject(RequestData { - processed_headers: processed.into(), + processed_headers: processed, pass_headers: None, - status: None + status: None, }); Ok(()) } @@ -70,19 +69,17 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> .unwrap() .explode_ref_mut(); - 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()) - )); - + 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()) + )); + let response = REQWEST_CLIENT .get() .unwrap() @@ -94,7 +91,10 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> req.payload_with_max_size(secure_max_size()) .await .map_err(|_| { - ErrorWithContext::new(Error::Generic("invalid_body".into()), "Couldn't parse request body.") + ErrorWithContext::new( + Error::Generic("invalid_body".into()), + "Couldn't parse request body.", + ) })? .to_vec(), ) @@ -109,7 +109,7 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> .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()); } @@ -117,7 +117,8 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> 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()); + resp.headers + .append(HeaderName::from_str(header).unwrap(), value.clone()); } }); } @@ -131,9 +132,13 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> resp.add_header( "x-bare-status-text", - response.status().canonical_reason().expect("canonical_reason should always exist."), + response + .status() + .canonical_reason() + .expect("canonical_reason should always exist."), true, - ).expect("Should never fail to add `x-bare-status-text`"); + ) + .expect("Should never fail to add `x-bare-status-text`"); add_cors_headers(resp); @@ -169,4 +174,4 @@ pub fn add_cors_headers_route(res: &mut Response) { .unwrap(); res.add_header("access-control-expose-headers", "*", true) .unwrap(); -} \ No newline at end of file +} From 88f4a077e99560a19e0837b20ab9983325843cfd Mon Sep 17 00:00:00 2001 From: UndefinedBHVR Date: Sun, 9 Jul 2023 23:43:27 -0500 Subject: [PATCH 4/6] Use temp error codes to prevent panics. --- src/error.rs | 18 ++++++++++-------- src/v3.rs | 1 - 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/error.rs b/src/error.rs index 618944b..fbede11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use std::fmt::format; + use miette::{Diagnostic, Report as StackReport}; use reqwest::StatusCode; use salvo::{ @@ -97,14 +99,14 @@ impl Error { Error::MissingBareHeader(header) | Error::InvalidBareHeader(header) => { format!("request.headers.{}", header.to_lowercase()) } - Error::ForbiddenBareHeader(_) => todo!(), - Error::UnknownBareHeader(_) => todo!(), - Error::InvalidHeader(_) => todo!(), - Error::HostNotFound => todo!(), - Error::ConnectionReset => todo!(), - Error::ConnectionRefused => todo!(), - Error::ConnectionTimeout => todo!(), - Error::Generic(_) => todo!(), + 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 => format!("error.http.not_found"), + Error::ConnectionReset => format!("error.http.reset"), + Error::ConnectionRefused => format!("error.http.refused"), + Error::ConnectionTimeout => format!("error.http.timeout"), + Error::Generic(kind) => format!("request.tomphttp-rs.{kind}"), }; json!({ diff --git a/src/v3.rs b/src/v3.rs index 29072c1..11adfd1 100644 --- a/src/v3.rs +++ b/src/v3.rs @@ -98,7 +98,6 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> })? .to_vec(), ) - // Send the new request and panic if it fails .send() .await .map_err(|e| { From e4ad937f07a1390720c6b207bf40c4ee642529cb Mon Sep 17 00:00:00 2001 From: UndefinedBHVR Date: Mon, 10 Jul 2023 00:40:16 -0500 Subject: [PATCH 5/6] Feature gate V2 --- Cargo.toml | 7 ++++++- src/error.rs | 10 ++++------ src/main.rs | 21 +++++++++++++++------ src/v3.rs | 27 +++++++++++++++++---------- src/version.rs | 9 ++++++++- 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d862c30..d6cc93b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,9 @@ serde_json = "1.0" reqwest = "0.11.18" once_cell = "1.17.1" thiserror = "1.0.40" -tracing-subscriber = "0.3.17" \ No newline at end of file +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/src/error.rs b/src/error.rs index fbede11..6c8c5fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,3 @@ -use std::fmt::format; - use miette::{Diagnostic, Report as StackReport}; use reqwest::StatusCode; use salvo::{ @@ -102,10 +100,10 @@ impl Error { 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 => format!("error.http.not_found"), - Error::ConnectionReset => format!("error.http.reset"), - Error::ConnectionRefused => format!("error.http.refused"), - Error::ConnectionTimeout => format!("error.http.timeout"), + 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}"), }; diff --git a/src/main.rs b/src/main.rs index 8c5919f..9571349 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ 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 error; @@ -32,21 +34,28 @@ async fn main() { .expect("This should never error"); tracing_subscriber::fmt::init(); tracing::info!("We gucchi"); - let app = Router::new() + + // 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("v2") - .hoop(crate::v3::process_headers) - .handle(crate::v3::fetch), - ) .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")] { + 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/v3.rs b/src/v3.rs index 11adfd1..4e92997 100644 --- a/src/v3.rs +++ b/src/v3.rs @@ -69,16 +69,23 @@ pub async fn fetch(req: &mut Request, depot: &mut Depot, resp: &mut Response) -> .unwrap() .explode_ref_mut(); - 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()) - )); + 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() diff --git a/src/version.rs b/src/version.rs index b24d3d8..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 3 of the [`TOMPHTTP`](https://github.com/tomphttp/specifications/blob/master/BareServerV2.md) specification - versions: vec!["v3".into(), "v2".into()], + versions, // The project is written in Rust language: "Rust".into(), // Use the default value for MaintainerData From 7f9180f63a67f7c23e916fa922a2de62597d3460 Mon Sep 17 00:00:00 2001 From: UndefinedBHVR Date: Mon, 10 Jul 2023 01:06:07 -0500 Subject: [PATCH 6/6] Notify when using v2 feature, work on readme.md --- README.md | 45 +++++++++++++++++++++++++++++++++++++++------ src/main.rs | 2 +- 2 files changed, 40 insertions(+), 7 deletions(-) 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/src/main.rs b/src/main.rs index 9571349..d585f82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,6 @@ async fn main() { .set(Client::new()) .expect("This should never error"); tracing_subscriber::fmt::init(); - tracing::info!("We gucchi"); // Compiler will complain. #[allow(unused_mut)] @@ -49,6 +48,7 @@ async fn main() { .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)