Skip to content

Commit

Permalink
Merge pull request #13 from Basicprogrammer10/dev
Browse files Browse the repository at this point in the history
🐶 Version 1.1.0
  • Loading branch information
connorslade authored Apr 10, 2022
2 parents 0b08328 + a49c5c6 commit f171098
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 85 deletions.
10 changes: 9 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# 1.0.0
# 1.1.0

- Update Path Matcher to support AnyAfter segments (**)
- Remove Test Example
- Add Paste Bin App Example
- Add SocketHandler struct to hold socket ineracting functions
- Fix Path Traversal Exploit O_O

# 1.0.0!

- Add ThreadPool Back!
- Tracing Feature
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,4 @@ server.route(Method::GET, "/", |_req| {
// This is blocking
server.start().unwrap();
```
/// Prelude
126 changes: 126 additions & 0 deletions examples/application_paste_bin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! A simple in memory pastebin backend
// If you want to make a real paste bin use a database for storage

// For a full pastebin front end and back end check out https://github.com/Basicprogrammer10/plaster-box
// Or try it out at https://paste.connorcode.com

use std::sync::{Arc, Mutex};
use std::time::Instant;

use afire::{Content, Method, Response, Server};

const DATA_LIMIT: usize = 1000;

const TIME_UNITS: &[(&str, u16)] = &[
("second", 60),
("minute", 60),
("hour", 24),
("day", 30),
("month", 12),
("year", 0),
];

struct Paste {
name: String,
body: String,
time: Instant,
}

fn main() {
let mut server = Server::new("localhost", 8080);
let pub_pastes = Arc::new(Mutex::new(Vec::new()));

// New paste Handler
let pastes = pub_pastes.clone();
server.route(Method::POST, "/new", move |req| {
// Make sure paste data isent too long
if req.body.len() > DATA_LIMIT {
return Response::new().status(400).text("Data too big!");
}

// Get the data as string
let body_str = match req.body_string() {
Some(i) => i,
None => return Response::new().status(400).text("Invalid Text"),
};

// Get the name from the Name header
let name = req.header("Name").unwrap_or_else(|| "Untitled".to_owned());

let paste = Paste {
name,
body: body_str,
time: Instant::now(),
};

// Push this paste to the pastes vector
let mut pastes = pastes.lock().unwrap();
let id = pastes.len();
pastes.push(paste);

// Send Redirect response
Response::new()
.status(301)
.text("Ok")
.header("Location", format!("/p/{}", id))
});

// Get pate handler
let pastes = pub_pastes.clone();
server.route(Method::GET, "/p/{id}", move |req| {
// Get is from path param
let id = req.path_param("id").unwrap().parse::<usize>().unwrap();

// Get the paste by id
let paste = &pastes.lock().unwrap()[id];

// Send paste
Response::new().text(&paste.body)
});

// View all pastes
let pastes = pub_pastes.clone();
server.route(Method::GET, "/pastes", move |_req| {
// Starter HTML
let mut out = String::from(
"<meta charset=\"UTF-8\"><table><tr><th>Name</th><th>Date</th><th>Link</th></tr>",
);

// Add a table row for each paste
for (i, e) in pastes.lock().unwrap().iter().enumerate() {
out.push_str(&format!(
"<tr><td>{}</td><td>{}</td><td><a href=\"/p/{}\">🔗</a></td></tr>",
e.name,
best_time(e.time.elapsed().as_secs()),
i
));
}

// Send HTML
Response::new()
.text(format!("{}</table>", out))
.content(Content::HTML)
});

server.start().unwrap();
}

// Turn seconds ago into a more readable relative time
// Ex 1 minute ago or 3 years ago
pub fn best_time(secs: u64) -> String {
let mut secs = secs as f64;

for i in TIME_UNITS {
if i.1 == 0 || secs < i.1 as f64 {
secs = secs.round();
return format!("{} {}{} ago", secs, i.0, if secs > 1.0 { "s" } else { "" });
}

secs /= i.1 as f64;
}

format!("{} years ago", secs.round())
}

// To use POST to /new with the body set to your paste data
// You can then GET /pastes to see all the pastes
42 changes: 0 additions & 42 deletions examples/test.rs

This file was deleted.

22 changes: 15 additions & 7 deletions lib/extensions/serve_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ impl ServeStatic {
fn process_req(req: Request, cell: &RwLock<ServeStatic>) -> (Response, bool) {
let this = cell.read().unwrap();

let mut path = format!("{}{}", this.data_dir, req.path.replace("/..", ""));
let mut path = format!("{}{}", this.data_dir, safe_path(req.path.to_owned()));

// Add Index.html if path ends with /
if path.ends_with('/') {
Expand Down Expand Up @@ -355,13 +355,21 @@ fn process_req(req: Request, cell: &RwLock<ServeStatic>) -> (Response, bool) {
}

fn get_type(path: &str, types: &[(String, String)]) -> String {
for i in types {
if i.0 == path.split('.').last().unwrap_or("") {
return i.1.to_owned();
}
}
let ext = path.split('.').last().unwrap_or("");
types
.iter()
.map(|x| x.to_owned())
.find(|x| x.0 == ext)
.unwrap_or_else(|| ("".to_owned(), "application/octet-stream".to_owned()))
.1
}

"application/octet-stream".to_owned()
#[inline]
fn safe_path(mut path: String) -> String {
while path.contains("/..") {
path = path.replace("/..", "");
}
path
}

/// Common MIME Types
Expand Down
14 changes: 8 additions & 6 deletions lib/handle.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Import STD libraries
use std::io::Read;
use std::net::TcpStream;

// Feature Imports
Expand All @@ -18,7 +17,7 @@ use crate::response::Response;
use crate::server::Server;

/// Handle a request
pub(crate) fn handle_connection(mut stream: &TcpStream, this: &Server) -> (Request, Response) {
pub(crate) fn handle_connection(stream: &mut TcpStream, this: &Server) -> (Request, Response) {
// Init (first) Buffer
let mut buffer = vec![0; this.buff_size];

Expand All @@ -28,9 +27,9 @@ pub(crate) fn handle_connection(mut stream: &TcpStream, this: &Server) -> (Reque
}

// Read stream into buffer
match stream.read(&mut buffer) {
Ok(_) => {}
Err(_) => {
match (this.socket_handler.socket_read)(stream, &mut buffer) {
Some(_) => {}
None => {
return (
Request::new_empty(),
Response::new()
Expand Down Expand Up @@ -65,11 +64,14 @@ pub(crate) fn handle_connection(mut stream: &TcpStream, this: &Server) -> (Reque

trim_end_bytes(&mut buffer);
let mut new_buffer = vec![0; new_buffer_size - buffer.len()];
stream.read_exact(&mut new_buffer).unwrap();
(this.socket_handler.socket_read_exact)(stream, &mut new_buffer).unwrap();
buffer.extend(new_buffer);
};
}

// Remove trailing null bytes
trim_end_bytes(&mut buffer);

// TODO: Parse Bytes
// TODO: Have one mut HTTP string that is chipted away at theough parseing

Expand Down
1 change: 1 addition & 0 deletions lib/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
pub mod common;
pub mod http;
pub mod path;
pub mod socket_handler;
39 changes: 20 additions & 19 deletions lib/internal/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pub enum PathPart {
/// Path param (/{name})
Param(String),

/// Match anything for self and after
AnyAfter,

/// Literally Anything (E)
Any,
}
Expand Down Expand Up @@ -52,29 +55,30 @@ impl Path {
let path = normalize_path(path);
let mut out = Vec::new();

// Bodge
if self.raw == "**" {
return Some(Vec::new());
}

let path = path.split('/');

if path.clone().count() != self.parts.len() {
return None;
}
let mut any_after = false;
for (i, j) in self.parts.iter().zip(path.clone()) {
if any_after {
continue;
}

for (i, j) in self.parts.iter().zip(path) {
match i {
PathPart::Normal(x) => {
if x != j {
return None;
}
}
PathPart::Param(x) => out.push((x.to_owned(), j.to_owned())),
PathPart::AnyAfter => any_after = true,
PathPart::Any => {}
}
}

if !any_after && path.count() != self.parts.len() {
return None;
}

Some(out)
}

Expand All @@ -92,21 +96,18 @@ impl PathPart {
/// Decode Path Segment into PathPart
#[cfg(feature = "path_patterns")]
pub fn from_segment(seg: &str) -> PathPart {
if seg == "*" {
return PathPart::Any;
}

if seg.starts_with('{') && seg.ends_with('}') {
return PathPart::Param(
seg.strip_prefix('{')
match seg {
"*" => PathPart::Any,
"**" => PathPart::AnyAfter,
x if x.starts_with('{') && x.ends_with('}') => PathPart::Param(
x.strip_prefix('{')
.unwrap()
.strip_suffix('}')
.unwrap()
.to_owned(),
);
),
_ => PathPart::Normal(seg.to_owned()),
}

PathPart::Normal(seg.to_owned())
}
}

Expand Down
32 changes: 32 additions & 0 deletions lib/internal/socket_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Hold methods for interacting with a TCP socket
//! Redefining the functions would allow adding TLS support to a afire server or other low level stuff
use std::io::{Read, Write};
use std::net::TcpStream;

/// Hold TCP socket read and write operations
// #[derive(Clone, Copy)]
pub struct SocketHandler {
/// Function for reading from a tcp socket
pub socket_read: Box<dyn Fn(&mut TcpStream, &mut Vec<u8>) -> Option<usize> + Send + Sync>,

/// Function for reading an exact ammout of bytes from a TCP socket
pub socket_read_exact: Box<dyn Fn(&mut TcpStream, &mut Vec<u8>) -> Option<()> + Send + Sync>,

/// Function for flushing a TCP socket
pub socket_flush: Box<dyn Fn(&mut TcpStream) -> Option<()> + Send + Sync>,

/// Function for writing to a TCP socket
pub socket_write: Box<dyn Fn(&mut TcpStream, &[u8]) -> Option<()> + Send + Sync>,
}

impl Default for SocketHandler {
fn default() -> Self {
Self {
socket_read: Box::new(|x, buff| x.read(buff).ok()),
socket_read_exact: Box::new(|x, buff| x.read_exact(buff).ok()),
socket_flush: Box::new(|x| x.flush().ok()),
socket_write: Box::new(|x, y| x.write_all(y).ok()),
}
}
}
Loading

0 comments on commit f171098

Please sign in to comment.