pushing near-usable frontend progress before attempting full refactor
This commit is contained in:
parent
caa6f98caa
commit
2229656a20
19
src/gem_fe.rs
Normal file
19
src/gem_fe.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 silt <silt@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
157
src/http_fe.rs
157
src/http_fe.rs
@ -20,12 +20,7 @@
|
|||||||
|
|
||||||
use httparse;
|
use httparse;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error, fmt, io::{self, BufRead, BufReader, Read}, net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, ops::Deref, path::{Component, PathBuf}, process::exit, str::FromStr, time::Duration
|
||||||
fmt,
|
|
||||||
io::{self, BufRead, BufReader, Read},
|
|
||||||
net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
|
|
||||||
process::exit,
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{eyap, yap};
|
use crate::{eyap, yap};
|
||||||
@ -33,7 +28,48 @@ use crate::{eyap, yap};
|
|||||||
pub use super::server::{Frontend, FrontendImpl};
|
pub use super::server::{Frontend, FrontendImpl};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum HttpErrorKind {
|
#[non_exhaustive]
|
||||||
|
pub enum HttpMethod {
|
||||||
|
GET,
|
||||||
|
POST,
|
||||||
|
HEAD,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for HttpMethod {
|
||||||
|
type Error = HttpError;
|
||||||
|
fn try_from(val: &str) -> Result<Self, Self::Error> {
|
||||||
|
use HttpMethod::*;
|
||||||
|
match val {
|
||||||
|
"GET" => Ok(GET),
|
||||||
|
"POST" => Ok(POST),
|
||||||
|
"HEAD" => Ok(HEAD),
|
||||||
|
_ => Err(HttpError {
|
||||||
|
kind: ResponseCode::MethodNotAllowed,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HttpMethod> for &str {
|
||||||
|
fn from(val: HttpMethod) -> Self {
|
||||||
|
use HttpMethod::*;
|
||||||
|
match val {
|
||||||
|
GET => "GET",
|
||||||
|
POST => "POST",
|
||||||
|
HEAD => "HEAD",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ResponseCode {
|
||||||
|
Okay,
|
||||||
|
Created,
|
||||||
|
MovedPermanently,
|
||||||
|
SeeOther,
|
||||||
|
TemporaryRedirect,
|
||||||
|
PermanentRedirect,
|
||||||
BadRequest,
|
BadRequest,
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
Forbidden,
|
Forbidden,
|
||||||
@ -46,10 +82,16 @@ pub enum HttpErrorKind {
|
|||||||
HttpVersionNotSupported,
|
HttpVersionNotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HttpErrorKind> for usize {
|
impl From<ResponseCode> for usize {
|
||||||
fn from(val: HttpErrorKind) -> Self {
|
fn from(val: ResponseCode) -> Self {
|
||||||
use HttpErrorKind::*;
|
use ResponseCode::*;
|
||||||
match val {
|
match val {
|
||||||
|
Okay => 200,
|
||||||
|
Created => 201,
|
||||||
|
MovedPermanently => 301,
|
||||||
|
SeeOther => 303,
|
||||||
|
TemporaryRedirect => 307,
|
||||||
|
PermanentRedirect => 308,
|
||||||
BadRequest => 400,
|
BadRequest => 400,
|
||||||
Unauthorized => 401,
|
Unauthorized => 401,
|
||||||
Forbidden => 403,
|
Forbidden => 403,
|
||||||
@ -64,10 +106,16 @@ impl From<HttpErrorKind> for usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HttpErrorKind> for &str {
|
impl From<ResponseCode> for &str {
|
||||||
fn from(val: HttpErrorKind) -> Self {
|
fn from(val: ResponseCode) -> Self {
|
||||||
use HttpErrorKind::*;
|
use ResponseCode::*;
|
||||||
match val {
|
match val {
|
||||||
|
Okay => "OK",
|
||||||
|
Created => "Created",
|
||||||
|
MovedPermanently => "Moved Permanently",
|
||||||
|
SeeOther => "See Other",
|
||||||
|
TemporaryRedirect => "Temporary Redirect",
|
||||||
|
PermanentRedirect => "Permanent Redirect",
|
||||||
BadRequest => "Bad Request",
|
BadRequest => "Bad Request",
|
||||||
Unauthorized => "Unauthorized",
|
Unauthorized => "Unauthorized",
|
||||||
Forbidden => "Forbidden",
|
Forbidden => "Forbidden",
|
||||||
@ -84,7 +132,13 @@ impl From<HttpErrorKind> for &str {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct HttpError {
|
pub struct HttpError {
|
||||||
kind: HttpErrorKind,
|
kind: ResponseCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpError {
|
||||||
|
pub fn new(kind: ResponseCode) -> Self {
|
||||||
|
Self { kind }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HttpError {
|
impl fmt::Display for HttpError {
|
||||||
@ -110,7 +164,7 @@ impl From<httparse::Error> for HttpError {
|
|||||||
fn from(value: httparse::Error) -> Self {
|
fn from(value: httparse::Error) -> Self {
|
||||||
HttpError {
|
HttpError {
|
||||||
kind: match value {
|
kind: match value {
|
||||||
_ => HttpErrorKind::BadRequest,
|
_ => ResponseCode::BadRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +195,39 @@ pub struct FeStorage {
|
|||||||
// TODO: tera template store
|
// TODO: tera template store
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frontend<FeStorage, FeConfig> {}
|
impl Frontend<FeStorage, FeConfig> {
|
||||||
|
fn router(
|
||||||
|
method: HttpMethod,
|
||||||
|
path: PathBuf,
|
||||||
|
params: Option<Vec<(&str, &str)>>,
|
||||||
|
headers: &[httparse::Header],
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
use HttpMethod::*;
|
||||||
|
// unwrapping is safe here because the resource path it came from is a valid UTF-8 &str
|
||||||
|
match (method, path.components().map(|c| c.as_os_str().to_str().unwrap()).collect::<Vec<&str>>().deref(), params, headers) {
|
||||||
|
(GET, ["/", "index.html"], _, _) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
(GET, ["/", "login.html"], _, _) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// can be cleaned up with rust/rust-lang #76001 or maybe #87599
|
||||||
|
(GET, ["/", user], _, _) if user.starts_with('~') => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
(GET, ["/", user, repo], _, _) if user.starts_with('~') => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
(GET, ["/", project], _, _) if project.starts_with('+') => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
(GET, ["/", project, repo], _, _) if project.starts_with('+') => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
_ => Err(Box::new(HttpError::new(ResponseCode::NotFound))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Iterator for Frontend<FeStorage, FeConfig> {
|
impl Iterator for Frontend<FeStorage, FeConfig> {
|
||||||
type Item = Incoming<'static>;
|
type Item = Incoming<'static>;
|
||||||
@ -178,6 +264,7 @@ impl FrontendImpl for Frontend<FeStorage, FeConfig> {
|
|||||||
let mut req = httparse::Request::new(&mut headers);
|
let mut req = httparse::Request::new(&mut headers);
|
||||||
let buf: &mut Vec<u8> = &mut vec![];
|
let buf: &mut Vec<u8> = &mut vec![];
|
||||||
|
|
||||||
|
// TODO: parse the rest of the request before sending to the router
|
||||||
stream_read.take(8192).read_until(b'\n', buf)?;
|
stream_read.take(8192).read_until(b'\n', buf)?;
|
||||||
let res = req.parse(buf);
|
let res = req.parse(buf);
|
||||||
|
|
||||||
@ -189,15 +276,43 @@ impl FrontendImpl for Frontend<FeStorage, FeConfig> {
|
|||||||
method: Some(method),
|
method: Some(method),
|
||||||
path: Some(path),
|
path: Some(path),
|
||||||
version: Some(1),
|
version: Some(1),
|
||||||
headers: _,
|
headers,
|
||||||
},
|
},
|
||||||
) => todo!(),
|
) => {
|
||||||
|
// separate path containing get params into path and kv vec
|
||||||
|
let (path, params) = path.split_once("?").map_or_else(
|
||||||
|
|| (PathBuf::from_str(path).unwrap(), None),
|
||||||
|
|(path, args)| {
|
||||||
|
(
|
||||||
|
PathBuf::from_str(path).unwrap(),
|
||||||
|
Some(
|
||||||
|
args.split('&')
|
||||||
|
.filter_map(|e| e.split_once('='))
|
||||||
|
.collect::<Vec<(&str, &str)>>(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if path.is_absolute() {
|
||||||
|
// context-valid lexical normalization without da feature
|
||||||
|
let path = PathBuf::from_iter(path.components().try_fold(Vec::<Component>::new(), |mut acc, item| -> Result<Vec<Component<'_>>, Box<dyn Error>> {
|
||||||
|
match item {
|
||||||
|
Component::CurDir => Ok(acc),
|
||||||
|
Component::RootDir => {acc.push(item); Ok(acc)},
|
||||||
|
Component::Normal(_) => {acc.push(item); Ok(acc)},
|
||||||
|
Component::ParentDir => {acc.pop_if(|c| c != &Component::RootDir); Ok(acc)},
|
||||||
|
Component::Prefix(_) => Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>),
|
||||||
|
}
|
||||||
|
})?);
|
||||||
|
Self::router(method.try_into()?, path, params, headers)
|
||||||
|
} else {
|
||||||
|
Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Malformed request lines and HTTP/1.1 requests without a Host header
|
// Malformed request lines and HTTP/1.1 requests without a Host header
|
||||||
(Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => {
|
(Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => {
|
||||||
Err(Box::new(HttpError {
|
Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>)
|
||||||
kind: HttpErrorKind::BadRequest,
|
|
||||||
}) as Box<dyn Error>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal parsing error; obvious bad request
|
// Fatal parsing error; obvious bad request
|
||||||
|
@ -21,11 +21,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
array,
|
array,
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{self, BufRead, BufReader, BufWriter, Read, Write},
|
io::{Read, Write},
|
||||||
net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
|
thread::{self, JoinHandle},
|
||||||
num::NonZeroUsize,
|
|
||||||
os::{linux::net::TcpStreamExt, unix::thread::JoinHandleExt},
|
|
||||||
thread::{self, JoinHandle, Thread},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{eyap, yap};
|
use crate::{eyap, yap};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user