Initial pukram server
This commit is contained in:
commit
c96ef60214
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
124
Cargo.lock
generated
Normal file
124
Cargo.lock
generated
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "formatting"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://gitlab.com/porky11/formatting#7edec9731c042bd2cdd7f3a1674cb2e1b0303e42"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maud"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0bab19cef8a7fe1c18a43e881793bfc9d4ea984befec3ae5bd0415abf3ecf00"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"maud_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maud_macros"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0be95d66c3024ffce639216058e5bae17a83ecaf266ffc6e4d060ad447c9eed2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pukram-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"maud",
|
||||||
|
"percent-encoding",
|
||||||
|
"pukram2html",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pukram2html"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://gitlab.com/porky11/pukram2html#8845a8f101cb302856443a01dee615db107e6ef9"
|
||||||
|
dependencies = [
|
||||||
|
"formatting",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "pukram-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
percent-encoding = "2.3"
|
||||||
|
maud = "0.25.0"
|
||||||
|
pukram2html = { git = "https://gitlab.com/porky11/pukram2html" }
|
||||||
232
src/main.rs
Normal file
232
src/main.rs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{hash_map::Entry, HashMap},
|
||||||
|
env,
|
||||||
|
fs::File,
|
||||||
|
io::{prelude::*, BufReader},
|
||||||
|
net::{TcpListener, TcpStream},
|
||||||
|
};
|
||||||
|
|
||||||
|
use maud::html;
|
||||||
|
use percent_encoding::percent_decode_str;
|
||||||
|
use pukram2html::{convert, convert_extended, convert_subheader, Settings};
|
||||||
|
|
||||||
|
mod request;
|
||||||
|
use request::Request;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut context = Context::default();
|
||||||
|
let mut args = env::args();
|
||||||
|
args.next();
|
||||||
|
let port = args.next().unwrap_or("8080".to_string());
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap();
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
let stream = stream.unwrap();
|
||||||
|
|
||||||
|
context.handle_connection(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fail(mut stream: TcpStream) {
|
||||||
|
let _ = write!(stream, "HTTP/1.1 404 Not Fonud\r\n\r\n");
|
||||||
|
let _ = writeln!(stream, "Page not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Comment {
|
||||||
|
name: String,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Context {
|
||||||
|
comments: HashMap<String, Vec<Comment>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
fn handle_connection(&mut self, mut stream: TcpStream) {
|
||||||
|
let Some(request) = Request::from(&stream) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(current_dir) = env::current_dir() else {
|
||||||
|
fail(stream);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut path = current_dir;
|
||||||
|
|
||||||
|
let (relative_path, _) = request
|
||||||
|
.path
|
||||||
|
.split_once('?')
|
||||||
|
.unwrap_or((&request.path, Default::default()));
|
||||||
|
|
||||||
|
if relative_path.contains("//") {
|
||||||
|
fail(stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let relative_path = if relative_path.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
percent_decode_str(&relative_path[1..])
|
||||||
|
.decode_utf8_lossy()
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
let (pk_file, pki_file, start_level) = if !relative_path.is_empty() {
|
||||||
|
path.push(&relative_path);
|
||||||
|
let (pk_extension, pki_extension) = if let Some(extension) = path.extension() {
|
||||||
|
(format!("{extension:?}.pk"), format!("{extension:?}.pki"))
|
||||||
|
} else {
|
||||||
|
("pk".to_string(), "pki".to_string())
|
||||||
|
};
|
||||||
|
let pk_path = path.with_extension(pk_extension);
|
||||||
|
let pki_path = path.with_extension(pki_extension);
|
||||||
|
|
||||||
|
let (Ok(pk_file), Ok(pki_file)) = (File::open(pk_path), File::open(pki_path)) else {
|
||||||
|
fail(stream);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
(pk_file, Some(pki_file), 1)
|
||||||
|
} else {
|
||||||
|
let mut pk_path = path.clone();
|
||||||
|
pk_path.push("index.pk");
|
||||||
|
|
||||||
|
let Ok(pk_file) = File::open(pk_path) else {
|
||||||
|
fail(stream);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
(pk_file, None, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
use Entry::*;
|
||||||
|
let local_comments = match self.comments.entry(relative_path.to_string()) {
|
||||||
|
Occupied(o) => o.into_mut(),
|
||||||
|
Vacant(v) => v.insert(Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut entries = request.body.split('&');
|
||||||
|
if let (Some(name_entry), Some(text_entry), None) =
|
||||||
|
(entries.next(), entries.next(), entries.next())
|
||||||
|
{
|
||||||
|
if let (Some(("name", input_name)), Some(("text", input_text))) =
|
||||||
|
(name_entry.split_once('='), text_entry.split_once('='))
|
||||||
|
{
|
||||||
|
let name_value = input_name.replace('+', " ");
|
||||||
|
let decoded_name = percent_decode_str(&name_value).decode_utf8_lossy();
|
||||||
|
let name = decoded_name
|
||||||
|
.replace('&', "&")
|
||||||
|
.replace('<', "<")
|
||||||
|
.replace('>', ">");
|
||||||
|
|
||||||
|
let text_value = input_text.replace('+', " ");
|
||||||
|
let decoded_text = percent_decode_str(&text_value).decode_utf8_lossy();
|
||||||
|
let mut text_buf = Vec::new();
|
||||||
|
convert(decoded_text.lines(), &mut text_buf);
|
||||||
|
let text = std::str::from_utf8(text_buf.as_slice())
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
local_comments.push(Comment { name, text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = write!(stream, "HTTP/1.1 200 OK\r\n\r\n");
|
||||||
|
|
||||||
|
let title = relative_path
|
||||||
|
.rsplit_once('/')
|
||||||
|
.map(|(_, title)| title)
|
||||||
|
.unwrap_or(&relative_path);
|
||||||
|
if !title.is_empty() {
|
||||||
|
let _ = write!(stream, "<h1>{title}</h1>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_ref = &path;
|
||||||
|
|
||||||
|
let handle_entry = |entry: &str, output: &mut TcpStream, level: usize| {
|
||||||
|
let level = level + 1;
|
||||||
|
let mut path = path_ref.clone();
|
||||||
|
let entry = if let Some((entry, _)) = entry.split_once(':') {
|
||||||
|
entry
|
||||||
|
} else {
|
||||||
|
entry
|
||||||
|
};
|
||||||
|
path.push(entry);
|
||||||
|
let extension = if let Some(extension) = path.extension() {
|
||||||
|
format!("{extension:?}.pki")
|
||||||
|
} else {
|
||||||
|
"pki".to_string()
|
||||||
|
};
|
||||||
|
path.set_extension(extension);
|
||||||
|
let Ok(file) = File::open(path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = writeln!(
|
||||||
|
output,
|
||||||
|
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
|
||||||
|
);
|
||||||
|
convert_subheader(
|
||||||
|
BufReader::new(file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.unwrap_or_default()),
|
||||||
|
output,
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
convert_extended(
|
||||||
|
BufReader::new(pk_file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.unwrap_or_default()),
|
||||||
|
&mut stream,
|
||||||
|
Settings::default()
|
||||||
|
.with_handler(handle_entry)
|
||||||
|
.with_start_level(start_level)
|
||||||
|
.with_use_textboxes(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
let parent_path = relative_path
|
||||||
|
.rsplit_once('/')
|
||||||
|
.map(|(path, _)| path)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let _ = writeln!(stream, "<hr>");
|
||||||
|
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
||||||
|
|
||||||
|
if let Some(pki_file) = pki_file {
|
||||||
|
let _ = writeln!(stream, "<h1>Description</h1>");
|
||||||
|
|
||||||
|
convert_subheader(
|
||||||
|
BufReader::new(pki_file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.unwrap_or_default()),
|
||||||
|
&mut stream,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = writeln!(stream, "<hr>");
|
||||||
|
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = html! {
|
||||||
|
h1 { "Comments" }
|
||||||
|
form method="POST" {
|
||||||
|
input type="text" name="name" value="anon" placeholder="Name";
|
||||||
|
br;
|
||||||
|
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
|
||||||
|
br;
|
||||||
|
input type="submit" value="Send!";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _ = stream.write_all(html.into_string().as_bytes());
|
||||||
|
|
||||||
|
for Comment { name, text } in local_comments {
|
||||||
|
let _ = writeln!(stream, "<fieldset><legend>{name}</legend>{text}</fieldset>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(stream, "<hr>");
|
||||||
|
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/request.rs
Normal file
60
src/request.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub method: String,
|
||||||
|
pub path: String,
|
||||||
|
pub version: String,
|
||||||
|
pub headers: Vec<String>,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
pub fn from(mut stream: &TcpStream) -> Option<Request> {
|
||||||
|
let mut buffer = [0; 16384];
|
||||||
|
let _ = stream.read(&mut buffer).ok()?;
|
||||||
|
|
||||||
|
let mut buffer_v = Vec::from_iter(buffer.iter().copied());
|
||||||
|
|
||||||
|
buffer_v.pop();
|
||||||
|
let raw_req = std::str::from_utf8(&buffer_v).ok()?.to_string();
|
||||||
|
let mut request_lines: Vec<&str> = raw_req.split("\r\n").collect();
|
||||||
|
|
||||||
|
let mut initial_line = request_lines[0].split(' ');
|
||||||
|
|
||||||
|
let method = initial_line.next().unwrap_or_default().to_string();
|
||||||
|
let path = initial_line.next().unwrap_or_default().to_string();
|
||||||
|
|
||||||
|
let version = if let Some(version) = initial_line.next() {
|
||||||
|
version
|
||||||
|
.split_once('/')
|
||||||
|
.map(|(_, version)| version)
|
||||||
|
.unwrap_or("1.1")
|
||||||
|
} else {
|
||||||
|
"1.1"
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let body = request_lines
|
||||||
|
.pop()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split_once('\0')
|
||||||
|
.map(|(body, _)| body)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
request_lines.pop();
|
||||||
|
|
||||||
|
let mut request_lines = request_lines.into_iter();
|
||||||
|
request_lines.next();
|
||||||
|
let headers: Vec<String> = request_lines.map(|header| header.to_string()).collect();
|
||||||
|
|
||||||
|
Some(Request {
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
version,
|
||||||
|
body,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user