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 std::{
 | 
			
		||||
    error::Error,
 | 
			
		||||
    fmt,
 | 
			
		||||
    io::{self, BufRead, BufReader, Read},
 | 
			
		||||
    net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
 | 
			
		||||
    process::exit,
 | 
			
		||||
    time::Duration,
 | 
			
		||||
    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
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{eyap, yap};
 | 
			
		||||
@ -33,7 +28,48 @@ use crate::{eyap, yap};
 | 
			
		||||
pub use super::server::{Frontend, FrontendImpl};
 | 
			
		||||
 | 
			
		||||
#[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,
 | 
			
		||||
    Unauthorized,
 | 
			
		||||
    Forbidden,
 | 
			
		||||
@ -46,10 +82,16 @@ pub enum HttpErrorKind {
 | 
			
		||||
    HttpVersionNotSupported,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<HttpErrorKind> for usize {
 | 
			
		||||
    fn from(val: HttpErrorKind) -> Self {
 | 
			
		||||
        use HttpErrorKind::*;
 | 
			
		||||
impl From<ResponseCode> for usize {
 | 
			
		||||
    fn from(val: ResponseCode) -> Self {
 | 
			
		||||
        use ResponseCode::*;
 | 
			
		||||
        match val {
 | 
			
		||||
        	Okay => 200,
 | 
			
		||||
        	Created => 201,
 | 
			
		||||
        	MovedPermanently => 301,
 | 
			
		||||
        	SeeOther => 303,
 | 
			
		||||
        	TemporaryRedirect => 307,
 | 
			
		||||
        	PermanentRedirect => 308,
 | 
			
		||||
            BadRequest => 400,
 | 
			
		||||
            Unauthorized => 401,
 | 
			
		||||
            Forbidden => 403,
 | 
			
		||||
@ -64,10 +106,16 @@ impl From<HttpErrorKind> for usize {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<HttpErrorKind> for &str {
 | 
			
		||||
    fn from(val: HttpErrorKind) -> Self {
 | 
			
		||||
        use HttpErrorKind::*;
 | 
			
		||||
impl From<ResponseCode> for &str {
 | 
			
		||||
    fn from(val: ResponseCode) -> Self {
 | 
			
		||||
        use ResponseCode::*;
 | 
			
		||||
        match val {
 | 
			
		||||
        	Okay => "OK",
 | 
			
		||||
        	Created => "Created",
 | 
			
		||||
        	MovedPermanently => "Moved Permanently",
 | 
			
		||||
        	SeeOther => "See Other",
 | 
			
		||||
        	TemporaryRedirect => "Temporary Redirect",
 | 
			
		||||
        	PermanentRedirect => "Permanent Redirect",
 | 
			
		||||
            BadRequest => "Bad Request",
 | 
			
		||||
            Unauthorized => "Unauthorized",
 | 
			
		||||
            Forbidden => "Forbidden",
 | 
			
		||||
@ -84,7 +132,13 @@ impl From<HttpErrorKind> for &str {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub struct HttpError {
 | 
			
		||||
    kind: HttpErrorKind,
 | 
			
		||||
    kind: ResponseCode,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HttpError {
 | 
			
		||||
    pub fn new(kind: ResponseCode) -> Self {
 | 
			
		||||
        Self { kind }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for HttpError {
 | 
			
		||||
@ -110,7 +164,7 @@ impl From<httparse::Error> for HttpError {
 | 
			
		||||
    fn from(value: httparse::Error) -> Self {
 | 
			
		||||
        HttpError {
 | 
			
		||||
            kind: match value {
 | 
			
		||||
                _ => HttpErrorKind::BadRequest,
 | 
			
		||||
                _ => ResponseCode::BadRequest,
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -141,7 +195,39 @@ pub struct FeStorage {
 | 
			
		||||
    // 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> {
 | 
			
		||||
    type Item = Incoming<'static>;
 | 
			
		||||
@ -178,6 +264,7 @@ impl FrontendImpl for Frontend<FeStorage, FeConfig> {
 | 
			
		||||
        let mut req = httparse::Request::new(&mut headers);
 | 
			
		||||
        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)?;
 | 
			
		||||
        let res = req.parse(buf);
 | 
			
		||||
 | 
			
		||||
@ -189,15 +276,43 @@ impl FrontendImpl for Frontend<FeStorage, FeConfig> {
 | 
			
		||||
                    method: Some(method),
 | 
			
		||||
                    path: Some(path),
 | 
			
		||||
                    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
 | 
			
		||||
            (Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => {
 | 
			
		||||
                Err(Box::new(HttpError {
 | 
			
		||||
                    kind: HttpErrorKind::BadRequest,
 | 
			
		||||
                }) as Box<dyn Error>)
 | 
			
		||||
                Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Fatal parsing error; obvious bad request
 | 
			
		||||
 | 
			
		||||
@ -21,11 +21,8 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    array,
 | 
			
		||||
    error::Error,
 | 
			
		||||
    io::{self, BufRead, BufReader, BufWriter, Read, Write},
 | 
			
		||||
    net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
 | 
			
		||||
    num::NonZeroUsize,
 | 
			
		||||
    os::{linux::net::TcpStreamExt, unix::thread::JoinHandleExt},
 | 
			
		||||
    thread::{self, JoinHandle, Thread},
 | 
			
		||||
    io::{Read, Write},
 | 
			
		||||
    thread::{self, JoinHandle},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{eyap, yap};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user