diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index a9d7db9..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# GitHub Copilot persisted chat sessions
-/copilot/chatSessions
diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
deleted file mode 100644
index 4c1cc2d..0000000
--- a/.idea/checkstyle-idea.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- 10.14.0
- JavaOnly
- true
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index c415781..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/udemy-course-server.iml b/.idea/udemy-course-server.iml
deleted file mode 100644
index f95d272..0000000
--- a/.idea/udemy-course-server.iml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..9e17ceb
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+ Your Portfolio
+
+
+
+
+ Your Name
+ Web Developer & Designer
+
+
+
+
+
Project 1
+
Description of your project goes here.
+
+
+
Project 2
+
Description of another project.
+
+
+
+
+
+
+
diff --git a/public/login.html b/public/login.html
new file mode 100644
index 0000000..ed48069
--- /dev/null
+++ b/public/login.html
@@ -0,0 +1,24 @@
+
+
+
+ Login
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/script.js b/public/script.js
new file mode 100644
index 0000000..a87f3bb
--- /dev/null
+++ b/public/script.js
@@ -0,0 +1,6 @@
+const button = document.querySelector('button');
+
+button.addEventListener('click', () => {
+ // Add functionality to the button click here (e.g., alert, change color)
+ alert('Button Clicked!');
+});
diff --git a/public/style.css b/public/style.css
new file mode 100644
index 0000000..fba6a9f
--- /dev/null
+++ b/public/style.css
@@ -0,0 +1,61 @@
+body {
+ margin: 0;
+ font-family: Arial, sans-serif;
+ }
+
+ .background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background: linear-gradient(to right, #f2f2f2, #eee);
+ animation: gradient 10s ease-in-out infinite alternate;
+ filter: blur(5px);
+ }
+
+ @keyframes gradient {
+ 0% {
+ background: linear-gradient(to right, #f2f2f2, #eee);
+ }
+ 50% {
+ background: linear-gradient(to right, #eee, #f7f7f7);
+ }
+ 100% {
+ background: linear-gradient(to right, #f7f7f7, #f2f2f2);
+ }
+ }
+
+ .content {
+ position: relative;
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 40px;
+ text-align: center;
+ color: #333;
+ }
+
+ h1 {
+ font-size: 3em;
+ margin-bottom: 20px;
+ }
+
+ p {
+ font-size: 1.2em;
+ line-height: 1.5;
+ }
+
+ button {
+ background-color: #4CAF50;
+ border: none;
+ color: white;
+ padding: 15px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 4px 2px;
+ cursor: pointer;
+ border-radius: 5px;
+ }
+
\ No newline at end of file
diff --git a/src/http/method.rs b/src/http/method.rs
index 936ceef..7142730 100644
--- a/src/http/method.rs
+++ b/src/http/method.rs
@@ -1,6 +1,10 @@
+use crate::http::ParseError;
+use std::str::FromStr;
+
+#[derive(Debug)]
pub enum Method {
- GET(String),
- DELETE(u64),
+ GET,
+ DELETE,
POST,
PUT,
HEAD,
@@ -8,4 +12,25 @@ pub enum Method {
OPTIONS,
TRACE,
PATCH,
-}
\ No newline at end of file
+}
+
+impl FromStr for Method {
+ type Err = MethodError;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "GET" => Ok(Self::GET),
+ "DELETE" => Ok(Self::DELETE),
+ "POST" => Ok(Self::POST),
+ "PUT" => Ok(Self::PUT),
+ "HEAD" => Ok(Self::HEAD),
+ "CONNECT" => Ok(Self::CONNECT),
+ "OPTIONS" => Ok(Self::OPTIONS),
+ "TRACE" => Ok(Self::TRACE),
+ "PATCH" => Ok(Self::PATCH),
+ _ => Err(MethodError)
+ }
+ }
+}
+
+pub struct MethodError;
\ No newline at end of file
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 9744b08..5a9d0bd 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -1,6 +1,12 @@
pub use request::Request;
pub use method::Method;
pub use request::ParseError;
+pub use query_string::{QueryString, Value as QueryStringValue};
+pub use response::Response;
+pub use status_code::StatusCode;
pub mod request;
-pub mod method;
\ No newline at end of file
+pub mod method;
+pub mod query_string;
+pub mod response;
+pub mod status_code;
\ No newline at end of file
diff --git a/src/http/query_string.rs b/src/http/query_string.rs
new file mode 100644
index 0000000..185fd3e
--- /dev/null
+++ b/src/http/query_string.rs
@@ -0,0 +1,41 @@
+use std::{collections::HashMap, thread::current};
+
+#[derive(Debug)]
+pub struct QueryString<'buffer_lifetime> {
+ data: HashMap<&'buffer_lifetime str, Value<'buffer_lifetime>>,
+}
+
+#[derive(Debug)]
+pub enum Value<'buffer_lifetime> {
+ Single(&'buffer_lifetime str),
+ Multiple(Vec<&'buffer_lifetime str>),
+}
+
+impl<'buffer_lifetime> QueryString<'buffer_lifetime> {
+ pub fn get(&self, key: &str) -> Option<&Value> {
+ self.data.get(key)
+ }
+}
+
+impl<'buffer_lifetime> From<&'buffer_lifetime str> for QueryString<'buffer_lifetime> {
+ fn from(value: &'buffer_lifetime str) -> Self {
+ let mut data = HashMap::new();
+
+ for sub_str in value.split('&') {
+ let mut key = sub_str;
+ let mut value = "";
+ if let Some(i) = sub_str.find('=') {
+ key = &sub_str[..i];
+ value = &sub_str[i + 1 ..]
+ }
+ data.entry(key).and_modify(|existing: &mut Value| match existing {
+ Value::Single(previous_value) => {
+ *existing = Value::Multiple(vec![previous_value, value]);
+ },
+ Value::Multiple(vec) => vec.push(value),
+ }).or_insert(Value::Single(key));
+ }
+
+ QueryString { data }
+ }
+}
diff --git a/src/http/request.rs b/src/http/request.rs
index 94c82a7..113bf05 100644
--- a/src/http/request.rs
+++ b/src/http/request.rs
@@ -1,35 +1,81 @@
-use std::str;
-use super::method::Method;
+use super::method::{Method, MethodError};
use std::convert::TryFrom;
-use std::io::Error;
-use std::fmt::{Display, Debug, Formatter};
+use std::fmt::{Debug, Display, Formatter};
+use std::str;
use std::str::Utf8Error;
+use crate::http::query_string::QueryString;
-pub struct Request {
- path: String,
- query_string: Option,
+#[derive(Debug)]
+pub struct Request<'buffer_lifetime> {
+ path: &'buffer_lifetime str,
+ query_string: Option>,
method: Method,
}
-impl Request {
+impl<'buffer_lifetime> Request<'buffer_lifetime> {
+ pub fn path(&self) -> &str {
+ self.path
+ }
+ pub fn query_string(&self) -> Option<&QueryString<'buffer_lifetime>> {
+ self.query_string.as_ref()
+ }
+
+ pub fn method(&self) -> &Method {
+ &self.method
+ }
}
-impl TryFrom<&[u8]> for Request {
+impl<'buffer_lifetime> TryFrom<&'buffer_lifetime [u8]> for Request<'buffer_lifetime> {
type Error = ParseError;
- //GET /search?name=abc&sort=1 HTTP/1.1
- fn try_from(buffer: &[u8]) -> Result {
- match str::from_utf8(buffer){
- Ok(request) => {}
- Err(_) => return Err(ParseError::InvalidEncoding),
- }
- match str::from_utf8(buffer).or(Err(ParseError::InvalidEncoding)) {
- Ok(request) => {}
- Err(e) => return Err(e)
+ //GET /search?name=abc&sort=1 HTTP/1.1\r\n...HEADERS...
+ fn try_from(buffer: &'buffer_lifetime [u8]) -> Result, Self::Error> {
+ let request = str::from_utf8(buffer)?;
+
+ let (method, request) = get_next_word(request).ok_or(ParseError::InvalidRequest)?;
+ let (mut path, request) = get_next_word(request).ok_or(ParseError::InvalidRequest)?;
+ let (protocol, _) = get_next_word(request).ok_or(ParseError::InvalidRequest)?;
+
+ if protocol != "HTTP/1.1" {
+ return Err(ParseError::InvalidProtocol);
}
- let request = str::from_utf8(buffer).or(Err(ParseError::InvalidEncoding))?;
+ let method: Method = method.parse()?;
+
+ let mut query_string = None;
+
+ if let Some(i) = path.find('?') {
+ query_string = Some(QueryString::from(&path[i + 1..]));
+ path = &path[..i];
+ }
+
+ return Ok(Self {
+ path,
+ query_string,
+ method,
+ });
+ }
+}
+
+fn get_next_word(request: &str) -> Option<(&str, &str)> {
+ for (index, request_character) in request.chars().enumerate() {
+ if request_character == ' ' || request_character == '\r' {
+ return Some((&request[..index], &request[index + 1..]));
+ }
+ }
+ None
+}
+
+impl From for ParseError {
+ fn from(value: MethodError) -> Self {
+ self::ParseError::InvalidMethod
+ }
+}
+
+impl From for ParseError {
+ fn from(_: Utf8Error) -> Self {
+ Self::InvalidEncoding
}
}
@@ -49,20 +95,16 @@ pub enum ParseError {
InvalidRequest,
InvalidEncoding,
InvalidProtocol,
- InvalidMethod
+ InvalidMethod,
}
-impl ParseError{
- fn message(&self) -> &str{
+impl ParseError {
+ fn message(&self) -> &str {
match self {
- ParseError::InvalidRequest => {"Invalid Request"}
- ParseError::InvalidEncoding => {"Invalid Encoding"}
- ParseError::InvalidProtocol => {"Invalid Protocol"}
- ParseError::InvalidMethod => {"Invalid Method"}
+ ParseError::InvalidRequest => "Invalid Request",
+ ParseError::InvalidEncoding => "Invalid Encoding",
+ ParseError::InvalidProtocol => "Invalid Protocol",
+ ParseError::InvalidMethod => "Invalid Method",
}
}
}
-
-impl Error for ParseError {
-
-}
diff --git a/src/http/response.rs b/src/http/response.rs
new file mode 100644
index 0000000..fe7ddab
--- /dev/null
+++ b/src/http/response.rs
@@ -0,0 +1,31 @@
+use std::{fmt::Display, net::TcpStream};
+use std::io::{Write, Result as IOResult};
+
+use super::status_code::StatusCode;
+
+#[derive(Debug)]
+pub struct Response {
+ status_code: StatusCode,
+ body: Option,
+}
+
+impl Response {
+ pub fn new(status_code: StatusCode, body: Option) -> Self {
+ Response { status_code, body }
+ }
+
+ pub fn send(&self, stream: &mut impl Write) -> IOResult<()>{
+ let body = match &self.body {
+ Some(body) => body,
+ None => "",
+ };
+
+ write!(
+ stream,
+ "HTTP/1.1 {} {}\r\n\r\n{}",
+ self.status_code,
+ self.status_code.reason_phrase(),
+ body
+ )
+ }
+}
diff --git a/src/http/status_code.rs b/src/http/status_code.rs
new file mode 100644
index 0000000..df436bc
--- /dev/null
+++ b/src/http/status_code.rs
@@ -0,0 +1,24 @@
+use std::fmt::{Display, Formatter, Result as FmtResult};
+
+#[derive(Clone, Copy, Debug)]
+pub enum StatusCode {
+ Ok = 200,
+ BadRequest = 400,
+ NotFound = 404
+}
+
+impl StatusCode {
+ pub fn reason_phrase(&self) -> &str {
+ match self {
+ Self::Ok => "Ok",
+ Self::BadRequest => "Bad Request",
+ Self::NotFound => "Not Found",
+ }
+ }
+}
+
+impl Display for StatusCode {
+ fn fmt(&self, f: &mut Formatter) -> FmtResult {
+ write!(f, "{}", *self as u16)
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index dc4de2d..37a2a70 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,20 @@
+#![allow(dead_code)]
+#![allow(unused_imports)]
+
mod server;
mod http;
+mod website_handler;
use server::Server;
use http::Request;
-use http::Method;
+use website_handler::WebsiteHandler;
+use std::env;
+//use http::Method;
fn main() {
+ let default_path = format!("{}\\public", env!("CARGO_MANIFEST_DIR").replace("/", "\\"));
+ let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
+ println!("public path is {}", public_path);
let server = Server::new(String::from("127.0.0.1:3000"));
- server.run();
+ server.run(WebsiteHandler::new(public_path));
}
\ No newline at end of file
diff --git a/src/server-test.http b/src/server-test.http
index e69de29..8021ed4 100644
--- a/src/server-test.http
+++ b/src/server-test.http
@@ -0,0 +1 @@
+GET localhost:3000
\ No newline at end of file
diff --git a/src/server.rs b/src/server.rs
index dd378b2..73eb07e 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,11 +1,21 @@
-use std::io::Read;
+use crate::http::{request, ParseError, Request, Response, StatusCode};
+use std::io::{Read, Write};
use std::net::TcpListener;
use std::process;
use std::ptr::read;
-use super::Request;
+
+// use super::Request;
use std::convert::TryFrom;
use std::convert::TryInto;
+pub trait Handler {
+ fn handle_request(&mut self, request: &Request) -> Response;
+ fn handle_bad_request(&mut self, error: &ParseError) -> Response {
+ println!("Failed to parse request: {}", error);
+ Response::new(StatusCode::BadRequest, None)
+ }
+}
+
/// A sample HTTP request looks like this:
/// ```
/// GET / HTTP/1.1\r\n
@@ -18,12 +28,10 @@ pub struct Server {
impl Server {
pub fn new(address: String) -> Self {
- Self {
- address,
- }
+ Self { address }
}
- pub fn run(self) {
+ pub fn run(self, mut handler: impl Handler) {
let (ip, port) = self.address.split_at(self.address.find(":").unwrap());
let port = &port[1..];
@@ -44,11 +52,15 @@ impl Server {
Ok(read_bytes) => {
println!("Read {} bytes", read_bytes);
println!("Received a request: {}", String::from_utf8_lossy(&buffer));
- match Request::try_from(&buffer[..]){
- Ok(request) => {},
- Err(e) => println!("Failed to parse a request: {e}")
+
+ let response = match Request::try_from(&buffer[..]) {
+ Ok(request) => handler.handle_request(&request),
+ Err(e) => handler.handle_bad_request(&e),
};
- let result: &Result = &buffer[..].try_into();
+
+ if let Err(e) = response.send(&mut stream) {
+ println!("Failed to send response: {}", e);
+ }
}
Err(error) => {
println!("Failed to read: {}", error);
@@ -62,4 +74,4 @@ impl Server {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/website_handler.rs b/src/website_handler.rs
new file mode 100644
index 0000000..99f98c6
--- /dev/null
+++ b/src/website_handler.rs
@@ -0,0 +1,73 @@
+use std::{fs, path};
+
+use crate::http::{Method, Request, Response, StatusCode};
+
+use super::server::Handler;
+
+pub struct WebsiteHandler {
+ public_path: String,
+}
+
+impl WebsiteHandler {
+ pub fn new(public_path: String) -> Self {
+ Self { public_path }
+ }
+
+ fn read_file(&self, file_path: &str) -> Option {
+ let path = format!("{}\\{}", self.public_path, file_path);
+
+ match fs::canonicalize(path) {
+ Ok(path) => {
+ if path.starts_with(&self.public_path) {
+ fs::read_to_string(path).ok()
+ } else {
+ println!("Directory Traversal Attack Attempted: {}", file_path);
+ Some("".to_string())
+ }
+ }
+ Err(_) => None,
+ }
+ }
+}
+
+impl Handler for WebsiteHandler {
+ fn handle_request(&mut self, request: &Request) -> Response {
+ match request.method() {
+ Method::GET => match request.path() {
+ "/" => Response::new(StatusCode::Ok, self.read_file("index.html")),
+ "/login" => Response::new(StatusCode::Ok, self.read_file("login.html")),
+ "/signup" => Response::new(
+ StatusCode::Ok,
+ Some(
+ r#"
+ Sign Up
+
+ "#
+ .to_string(),
+ ),
+ ),
+ path => match self.read_file(path) {
+ Some(contents) => Response::new(StatusCode::Ok, Some(contents)),
+ None => Response::new(StatusCode::NotFound, None),
+ },
+ },
+ Method::DELETE => todo!(),
+ Method::POST => todo!(),
+ Method::PUT => todo!(),
+ Method::HEAD => todo!(),
+ Method::CONNECT => todo!(),
+ Method::OPTIONS => todo!(),
+ Method::TRACE => todo!(),
+ Method::PATCH => todo!(),
+ _ => Response::new(StatusCode::NotFound, None),
+ }
+ }
+}