Commented and Refactored codeMade ids in the database i64 values to increase available id spaceMoved some structs and enums from main.rs to models.rsCommented structs and functions

This commit is contained in:
Camerin Figueroa 2024-03-06 22:43:16 +00:00
parent 183da5e38c
commit da0f1ddf2d
8 changed files with 321 additions and 138 deletions

76
Cargo.lock generated
View File

@ -591,6 +591,18 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "hyper-sync-rustls"
version = "0.3.0-rc.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d1a443a90413a118ac6739e024f6a5180aa3b3f43f7de65f9d388a961cff19b"
dependencies = [
"hyper",
"rustls",
"webpki",
"webpki-roots",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.60" version = "0.1.60"
@ -1150,6 +1162,18 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "ring"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4db68a2e35f3497146b7e4563df7d4773a2433230c5e4b448328e31740458a"
dependencies = [
"cc",
"lazy_static",
"libc",
"untrusted",
]
[[package]] [[package]]
name = "rocket" name = "rocket"
version = "0.4.11" version = "0.4.11"
@ -1210,9 +1234,11 @@ checksum = "2bf9cbd128e1f321a2d0bebd2b7cf0aafd89ca43edf69e49b56a5c46e48eb19f"
dependencies = [ dependencies = [
"cookie", "cookie",
"hyper", "hyper",
"hyper-sync-rustls",
"indexmap", "indexmap",
"pear", "pear",
"percent-encoding 1.0.1", "percent-encoding 1.0.1",
"rustls",
"smallvec", "smallvec",
"state", "state",
"time", "time",
@ -1225,6 +1251,20 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustls"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7891791343c75b73ed9a18cadcafd8c8563d11a88ebe2d87f5b8a3182654d9"
dependencies = [
"base64 0.9.3",
"log 0.4.20",
"ring",
"sct",
"untrusted",
"webpki",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
@ -1252,6 +1292,16 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb8f61f9e6eadd062a71c380043d28036304a4706b3c4dd001ff3387ed00745a"
dependencies = [
"ring",
"untrusted",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.197" version = "1.0.197"
@ -1611,6 +1661,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "untrusted"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
[[package]] [[package]]
name = "url" name = "url"
version = "1.7.2" version = "1.7.2"
@ -1738,6 +1794,26 @@ version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]]
name = "webpki"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d7967316d8411ca3b01821ee6c332bde138ba4363becdb492f12e514daa17f"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85d1f408918fd590908a70d36b7ac388db2edc221470333e4d6e5b598e44cabf"
dependencies = [
"untrusted",
"webpki",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.2.8" version = "0.2.8"

View File

@ -9,13 +9,13 @@ edition = "2021"
chrono = "0.4.34" chrono = "0.4.34"
diesel = { version = "2.1.4", features = ["postgres", "chrono"] } diesel = { version = "2.1.4", features = ["postgres", "chrono"] }
dotenvy = "0.15.7" dotenvy = "0.15.7"
rocket = "0.4.10" rocket = { version = "0.4.11", features = ["tls"] }
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114" serde_json = "1.0.114"
tokio = { version = "1.36.0", features = ["full"] } tokio = { version = "1.36.0", features = ["full"] }
[dependencies.rocket_contrib] [dependencies.rocket_contrib]
version = "0.4.10" version = "0.4.11"
features = ["handlebars_templates", "tera_templates"] features = ["handlebars_templates", "tera_templates"]
[dependencies.uuid] [dependencies.uuid]

27
Rocket.toml Normal file
View File

@ -0,0 +1,27 @@
[development]
address = "0.0.0.0"
port = 8000
keep_alive = 5
read_timeout = 5
write_timeout = 5
log = "normal"
limits = { forms = 32768 }
[staging]
address = "0.0.0.0"
port = 8000
keep_alive = 5
read_timeout = 5
write_timeout = 5
log = "normal"
limits = { forms = 32768 }
[production]
address = "0.0.0.0"
port = 80
keep_alive = 5
read_timeout = 5
write_timeout = 5
log = "critical"
limits = { forms = 32768 }
template_dir = "/templates/"

View File

@ -1,21 +1,21 @@
-- Your SQL goes here -- Your SQL goes here
CREATE TABLE IF NOT EXISTS clients CREATE TABLE IF NOT EXISTS clients
( (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
ip TEXT NOT NULL, ip TEXT NOT NULL,
mirror_id TEXT NOT NULL client_id TEXT NOT NULL
); );
CREATE TABLE IF NOT EXISTS ownership CREATE TABLE IF NOT EXISTS ownership
( (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
owner_id TEXT NOT NULL, owner_id TEXT NOT NULL,
client_id TEXT NOT NULL client_id TEXT NOT NULL
); );
CREATE TABLE IF NOT EXISTS history CREATE TABLE IF NOT EXISTS history
( (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
client_id TEXT NOT NULL, client_id TEXT NOT NULL,
request_type TEXT NOT NULL, request_type TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL timestamp TIMESTAMP NOT NULL
@ -23,9 +23,9 @@ CREATE TABLE IF NOT EXISTS history
CREATE TABLE IF NOT EXISTS pair_records CREATE TABLE IF NOT EXISTS pair_records
( (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
history_id INTEGER NOT NULL, history_id BIGINT NOT NULL,
pair_type INTEGER NOT NULL, pair_type SMALLINT NOT NULL,
key TEXT NOT NULL, key TEXT NOT NULL,
value TEXT NOT NULL value TEXT NOT NULL
); );

View File

@ -3,16 +3,23 @@ pub mod schema;
use chrono::offset::Utc; use chrono::offset::Utc;
use diesel::prelude::*; use diesel::prelude::*;
use std::env;
use dotenvy::dotenv; use dotenvy::dotenv;
use models::{ use models::{
Client, NewClient, NewHistoryRecord, NewPairRecord Client, NewClient, NewHistoryRecord, NewPairRecord, PairType
}; };
use schema::{ use schema::{
clients, history, pair_records clients, history, pair_records
}; };
use std::env;
/// Establishes diesel Postgres connection that can be used to iteract with the database
///
/// Example:
/// ```
/// let connection = establish_connection();
/// ```
pub fn establish_connection() -> PgConnection { pub fn establish_connection() -> PgConnection {
dotenv().ok(); dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
@ -20,17 +27,30 @@ pub fn establish_connection() -> PgConnection {
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
} }
pub fn create_client(conn: &mut PgConnection, ip: &str, mirror_id: &str) -> usize { /// Used to create a new client in the database. You need to pass a connection, the ip and client_id
let new_client = NewClient {ip: &ip, mirror_id: &mirror_id}; ///
/// Example:
/// ```
/// let connection = establish_connection();
///
/// create_client(connection, "192.168.0.1", "195222-222-123123");
/// ```
///
/// create_client returns a value of usize which represents the number of entries created
pub fn create_client(conn: &mut PgConnection, ip: &str, client_id: &str) -> usize {
let new_client = NewClient {ip: &ip, client_id: &client_id};
diesel::insert_into(clients::table) diesel::insert_into(clients::table)
.values(&new_client) .values(&new_client)
.returning(Client::as_returning()) .returning(Client::as_returning())
.execute(conn) .execute(conn)
.expect("Error") .expect("Error: Couldn't create a new client in the database.")
} }
pub fn create_history_record(conn: &mut PgConnection, client_id: &str, request_type: &str, ) -> i32 { /// Creates a new history record in the database. A history record is the initial record to store a
/// request that is sent to the application.
/// The history record has a client_id, request_type, and a timestamp.
pub fn create_history_record(conn: &mut PgConnection, client_id: &str, request_type: &str, ) -> i64 {
let new_history_record = NewHistoryRecord { let new_history_record = NewHistoryRecord {
client_id: &client_id, client_id: &client_id,
request_type: request_type, request_type: request_type,
@ -44,8 +64,11 @@ pub fn create_history_record(conn: &mut PgConnection, client_id: &str, request_t
.get_result(conn).unwrap() .get_result(conn).unwrap()
} }
pub fn create_pair_record(conn: &mut PgConnection, history_id: i32, pair_type: i32, key: &str, value: &str) -> usize { /// Creates a new pair record in the database.
let new_pair_record = NewPairRecord {history_id: history_id, pair_type: pair_type, key: &key, value: &value}; /// Pass the history_id that this pair will be associated, the PairType, key and value of the pair
/// pair_type is automatically casted as i32 type
pub fn create_pair_record(conn: &mut PgConnection, history_id: i64, pair_type: PairType, key: &str, value: &str) -> usize {
let new_pair_record = NewPairRecord {history_id: history_id, pair_type: pair_type as i16, key: &key, value: &value};
diesel::insert_into(pair_records::table) diesel::insert_into(pair_records::table)
.values(&new_pair_record) .values(&new_pair_record)

View File

@ -16,20 +16,6 @@ use request_mirror::models::*;
use diesel::prelude::*; use diesel::prelude::*;
use request_mirror::*; use request_mirror::*;
#[derive(Serialize, Debug, Clone)]
enum PairType {
Header,
Cookie,
Query,
Body
}
#[derive(Serialize, Debug, Clone)]
struct Pair {
key: String,
value: String
}
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
struct RequestInfo { struct RequestInfo {
header: Vec<Pair>, header: Vec<Pair>,
@ -37,17 +23,12 @@ struct RequestInfo {
query: Vec<Pair> query: Vec<Pair>
} }
#[derive(Serialize, Debug, Clone)]
struct RequestBody(String);
#[derive(Debug)] #[derive(Debug)]
enum ApiError { enum ApiError {
} }
#[derive(Serialize)]
struct ErrorContext { #[derive(Serialize, Debug, Clone)]
error_msg: String struct RequestBody(String);
}
// Always use a limit to prevent DoS attacks. // Always use a limit to prevent DoS attacks.
const LIMIT: u64 = 256; const LIMIT: u64 = 256;
@ -55,15 +36,16 @@ const LIMIT: u64 = 256;
impl<'a, 'r> FromRequest<'a, 'r> for RequestInfo { impl<'a, 'r> FromRequest<'a, 'r> for RequestInfo {
type Error = ApiError; type Error = ApiError;
/// Used for parsing request information and making it available for request functions to access
/// Also handles creating cookies when the client doesn't send one in the request
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let mut req_cookies = request.cookies(); let mut req_cookies = request.cookies();
for row in req_cookies.iter() {
println!("{}: {}", row.name(), row.value());
}
// Initially set cookie // Initially set cookie
if req_cookies.get(&"mirror-id").is_none() { if req_cookies.get(&"mirror-id").is_none() {
// When the client doesn't send the mirror-id cookie, from_request will create
// one in the database and send it back to the client.
let new_uuid = Uuid::new_v4().to_string(); let new_uuid = Uuid::new_v4().to_string();
println!("Creating new cookie"); println!("Creating new cookie");
@ -78,9 +60,12 @@ impl<'a, 'r> FromRequest<'a, 'r> for RequestInfo {
let connection = &mut establish_connection(); let connection = &mut establish_connection();
// Creates a new client record in the database
create_client(connection, &address, &new_uuid); create_client(connection, &address, &new_uuid);
} }
// Compile vector of Pair structs with each header, cookie and query param that is coming
// from the client
let mut header: Vec<Pair> = vec![]; let mut header: Vec<Pair> = vec![];
let mut cookies: Vec<Pair> = vec![]; let mut cookies: Vec<Pair> = vec![];
let mut query: Vec<Pair> = vec![]; let mut query: Vec<Pair> = vec![];
@ -111,7 +96,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for RequestInfo {
} }
} }
Outcome::Success(RequestInfo{ // Send the Request Info as successful outcome
Outcome::Success(RequestInfo {
header: header, header: header,
cookies: cookies, cookies: cookies,
query: query query: query
@ -122,6 +108,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for RequestInfo {
impl FromDataSimple for RequestBody { impl FromDataSimple for RequestBody {
type Error = String; type Error = String;
/// Used to extract the body of a request and make it available to request functions
fn from_data(_req: &Request, data: Data) -> data::Outcome<Self, String> { fn from_data(_req: &Request, data: Data) -> data::Outcome<Self, String> {
// Read the data into a String. // Read the data into a String.
let mut string = String::new(); let mut string = String::new();
@ -134,11 +121,10 @@ impl FromDataSimple for RequestBody {
} }
} }
/// Returns the index template
#[get("/")] #[get("/")]
fn index(_info: RequestInfo) -> Template { fn index(_info: RequestInfo) -> Template {
//Redirect::to("/test")
#[derive(Serialize)] #[derive(Serialize)]
struct Context{ struct Context{
} }
@ -146,37 +132,38 @@ fn index(_info: RequestInfo) -> Template {
Template::render("index", Context {}) Template::render("index", Context {})
} }
/// Processes /test get request and responds with some of the contents of the request
/// This function also stores information from the request in the database
#[get("/test")] #[get("/test")]
fn test_get(request: RequestInfo, cookies: Cookies) -> Template { fn test_get(request: RequestInfo, cookies: Cookies) -> Template {
println!("{request:?}"); let client_id = cookies.get("mirror-id");
let cookie_id = cookies.get("mirror-id"); // If the cookie exists, create new database records for this request
if client_id.is_some() {
if cookie_id.is_some() {
let connection = &mut establish_connection(); let connection = &mut establish_connection();
let history_id = create_history_record(connection, cookie_id.unwrap().value(), "Get");
println!("Creating header Records for {history_id}"); // Create new history record and retrieve new history_id
let history_id = create_history_record(connection, client_id.unwrap().value(), "Get");
// Create header pair records
for row in &request.header { for row in &request.header {
create_pair_record(connection, history_id, PairType::Header as i32, &row.key, &row.value); create_pair_record(connection, history_id, PairType::Header, &row.key, &row.value);
} }
println!("Creating cookie Records for {history_id}"); // Create cookie pair records
for row in &request.cookies { for row in &request.cookies {
create_pair_record(connection, history_id, PairType::Cookie as i32, &row.key, &row.value); create_pair_record(connection, history_id, PairType::Cookie, &row.key, &row.value);
} }
println!("Creating query Records for {history_id}"); // Create query pair records
for row in &request.query { for row in &request.query {
create_pair_record(connection, history_id, PairType::Query as i32, &row.key, &row.value); create_pair_record(connection, history_id, PairType::Query, &row.key, &row.value);
} }
} }
// Define context for the template
#[derive(Serialize)] #[derive(Serialize)]
struct Context<'a> { struct Context<'a> {
request_type: &'a str, request_type: &'a str,
@ -185,6 +172,7 @@ fn test_get(request: RequestInfo, cookies: Cookies) -> Template {
query: Vec<Pair>, query: Vec<Pair>,
} }
// Compile context
let context = Context { let context = Context {
request_type: "Get", request_type: "Get",
header: request.header, header: request.header,
@ -192,45 +180,46 @@ fn test_get(request: RequestInfo, cookies: Cookies) -> Template {
query: request.query query: request.query
}; };
// Render request_details
Template::render("request_details", context) Template::render("request_details", context)
} }
/// Processes /test post request and responds with some of the contents of the request
/// This function also stores information from the request in the database
#[post("/test", data = "<body>")] #[post("/test", data = "<body>")]
fn test_post(body: RequestBody, request: RequestInfo, cookies: Cookies) -> Template { fn test_post(body: RequestBody, request: RequestInfo, cookies: Cookies) -> Template {
println!("{request:?}"); let client_id = cookies.get("mirror-id");
println!("Input: {}", body.0); // If the cookie exists, create new database records for this request
if client_id.is_some() {
let cookie_id = cookies.get("mirror-id");
if cookie_id.is_some() {
let connection = &mut establish_connection(); let connection = &mut establish_connection();
let history_id = create_history_record(connection, cookie_id.unwrap().value(), "Post");
// Create new history record and retrieve new history_id
println!("Creating header Records for {history_id}"); let history_id = create_history_record(connection, client_id.unwrap().value(), "Post");
// Create header pair records
for row in &request.header { for row in &request.header {
create_pair_record(connection, history_id, PairType::Header as i32, &row.key, &row.value); create_pair_record(connection, history_id, PairType::Header, &row.key, &row.value);
} }
println!("Creating cookie Records for {history_id}"); // Create cookie pair records
for row in &request.cookies { for row in &request.cookies {
create_pair_record(connection, history_id, PairType::Cookie as i32, &row.key, &row.value); create_pair_record(connection, history_id, PairType::Cookie, &row.key, &row.value);
} }
println!("Creating query Records for {history_id}"); // Create query pair records
for row in &request.query { for row in &request.query {
create_pair_record(connection, history_id, PairType::Query as i32, &row.key, &row.value); create_pair_record(connection, history_id, PairType::Query, &row.key, &row.value);
} }
// Create a pair record for body of the request
println!("Creating body Records for {history_id}"); println!("Creating body Records for {history_id}");
create_pair_record(connection, history_id, PairType::Body as i32, "body", &body.0.clone()); create_pair_record(connection, history_id, PairType::Body, "body", &body.0.clone());
} }
// Define context for the template
#[derive(Serialize)] #[derive(Serialize)]
struct Context<'a> { struct Context<'a> {
request_type: &'a str, request_type: &'a str,
@ -240,6 +229,7 @@ fn test_post(body: RequestBody, request: RequestInfo, cookies: Cookies) -> Templ
body: String body: String
} }
// Compile context
let context = Context { let context = Context {
request_type: "Post", request_type: "Post",
header: request.header, header: request.header,
@ -248,113 +238,122 @@ fn test_post(body: RequestBody, request: RequestInfo, cookies: Cookies) -> Templ
body: body.0 body: body.0
}; };
// Render request_details Template
Template::render("request_details", context) Template::render("request_details", context)
} }
/// Request function that returns a history of requests that the current client has made
/// The user can click a history_id and view the request itself
#[get("/history")] #[get("/history")]
fn history_req(cookies: Cookies) -> Template { fn history_req(cookies: Cookies) -> Template {
let cookie_id = cookies.get("mirror-id"); // Get the client_id from the cookies
let client_id = cookies.get("mirror-id").unwrap().value();
let cookie_id = cookie_id.unwrap().value();
println!("Client ID: {}", cookie_id);
let connection = &mut establish_connection(); let connection = &mut establish_connection();
// Query the database for history records
let results = history::dsl::history let results = history::dsl::history
.filter(history::client_id.eq(cookie_id.to_string())) .filter(history::client_id.eq(client_id.to_string()))
.select(HistoryRecord::as_select()) .select(HistoryRecord::as_select())
.load(connection) .load(connection)
.expect("Error loading clients"); .expect("Error loading clients");
for record in results.iter() { /// Template Context
println!("{:?}", record);
}
#[derive(Serialize)]
struct History {
pub id: i32,
pub client_id: String,
pub request_type: String,
pub timestamp: String
}
#[derive(Serialize)] #[derive(Serialize)]
struct Context { struct Context {
history_records: Vec<History> history_records: Vec<History>
} }
// New vector to store converted History structs
let mut history_records: Vec<History> = Vec::new(); let mut history_records: Vec<History> = Vec::new();
// For each HistoryRecord, create a new History struct
for history_rec in results { for history_rec in results {
history_records.push( history_records.push(
History { History {
id: history_rec.id, id: history_rec.id,
client_id: history_rec.client_id, client_id: history_rec.client_id,
request_type: history_rec.request_type, request_type: history_rec.request_type,
timestamp: history_rec.timestamp.format("%Y-%m-%d %H:%M:%S UTC").to_string() timestamp: history_rec.timestamp.format("%Y-%m-%d %H:%M:%S UTC").to_string() // convert to string
} }
); );
} }
Template::render("history", Context{ // Render the template
history_records: history_records Template::render(
}) "history",
Context{
history_records: history_records
}
)
} }
/// The history_details request function will provide a webpage that a user can view the contents of a
/// request that was recorded to the database.
/// This includes the body of a post request, headers, cookies and query parameters.
#[get("/history/<history_id>")] #[get("/history/<history_id>")]
fn history_details(history_id: i32, cookies: Cookies) -> Template { fn history_details(history_id: i64, cookies: Cookies) -> Template {
let cookie_id = cookies.get("mirror-id"); let client_id = cookies.get("mirror-id").unwrap().value();
let cookie_id = cookie_id.unwrap().value();
println!("Client ID: {}", cookie_id);
let connection = &mut establish_connection(); let connection = &mut establish_connection();
// Query history table where the history id is what was in the route. Also filter client_ids.
// If this record has a different client_id nothing will be returned
let results: Vec<HistoryRecord> = history::dsl::history let results: Vec<HistoryRecord> = history::dsl::history
.filter(history::id.eq(&history_id)) .filter(history::id.eq(&history_id))
.filter(history::client_id.eq(cookie_id.to_string())) .filter(history::client_id.eq(client_id.to_string()))
.select(HistoryRecord::as_select()) .select(HistoryRecord::as_select())
.load(connection) .load(connection)
.expect("Error loading history records"); .expect("Error loading history records");
if results.len() <= 0 { if results.len() <= 0 {
// Error // Error when no results are returned
return Template::render( return Template::render(
"error", "error",
ErrorContext{ error_msg: "No Results Found. You may be unauthorized...".to_string() } ErrorContext{ error_msg: "No Results Found. You may be not be authorized to view this record...".to_string() }
); );
} }
let connection = &mut establish_connection(); let connection = &mut establish_connection();
// Query all Pair Records for this history_id
let pairs: Vec<PairRecord> = pair_records::dsl::pair_records let pairs: Vec<PairRecord> = pair_records::dsl::pair_records
.filter(pair_records::history_id.eq(history_id)) .filter(pair_records::history_id.eq(history_id))
.select(PairRecord::as_select()) .select(PairRecord::as_select())
.load(connection) .load(connection)
.expect("Error loading history records"); .expect("Error loading history records");
let body: String = match &pairs.iter().filter(|res| res.pair_type == PairType::Body as i32).last() { // Filter Body Pair Records for this history_id
Some(pair) => pair.value.clone(), let body: String = match &pairs.iter()
_ => "".to_string() .filter(|res| res.pair_type == PairType::Body as i16).last() // Filter and get last record
{
Some(pair) => pair.value.clone().to_string(), // Return pair value if Some
_ => "".to_string() // Return empty string if None
}; };
// Collect Header Pair Records into a vector
let header: Vec<&PairRecord> = pairs.iter() let header: Vec<&PairRecord> = pairs.iter()
.filter(|res: &&PairRecord| res.pair_type == PairType::Header as i32) .filter(|res: &&PairRecord| res.pair_type == PairType::Header as i16)
.map(|res: &PairRecord| res) .map(|res: &PairRecord| res)
.collect(); .collect();
// Collect Cookie Pair Records into a vector
let cookies: Vec<&PairRecord> = pairs.iter() let cookies: Vec<&PairRecord> = pairs.iter()
.filter(|res: &&PairRecord| res.pair_type == PairType::Cookie as i32) .filter(|res: &&PairRecord| res.pair_type == PairType::Cookie as i16)
.map(|res: &PairRecord| res)
.collect();
let query: Vec<&PairRecord> = pairs.iter()
.filter(|res: &&PairRecord| res.pair_type == PairType::Query as i32)
.map(|res: &PairRecord| res) .map(|res: &PairRecord| res)
.collect(); .collect();
// Collect Query Pair Records into a vector
let query: Vec<&PairRecord> = pairs.iter()
.filter(|res: &&PairRecord| res.pair_type == PairType::Query as i16)
.map(|res: &PairRecord| res)
.collect();
/// history_details specific context
#[derive(Serialize)] #[derive(Serialize)]
struct Context<'a> { struct Context<'a> {
request_type: String, request_type: String,
@ -364,11 +363,12 @@ fn history_details(history_id: i32, cookies: Cookies) -> Template {
query: Vec<&'a PairRecord>, query: Vec<&'a PairRecord>,
} }
// Render request_details with data taken from the database
Template::render( Template::render(
"request_details", "request_details",
Context{ Context{
request_type: results[0].request_type.clone(), request_type: "Previous ".to_owned() + &results[0].request_type.clone(),
body: body.to_string(), body: body,
header: header, header: header,
cookies: cookies, cookies: cookies,
query: query query: query
@ -377,17 +377,30 @@ fn history_details(history_id: i32, cookies: Cookies) -> Template {
} }
/// 404 Response
#[catch(404)] #[catch(404)]
fn not_found(req: &Request) -> String { fn not_found(req: &Request) -> Template {
print!("{}", req); Template::render(
format!("Oh no! We couldn't find the requested path '{}'", req.uri()) "error",
ErrorContext{
error_msg: format!("Oh no! We couldn't find the requested path '{}'", req.uri())
}
)
} }
fn main() { fn main() {
rocket::ignite() rocket::ignite()
.register(catchers![not_found]) .register(catchers![not_found])
.mount("/", routes![index, test_get, test_post, history_req, history_details]) .mount(
"/",
routes![
index,
test_get,
test_post,
history_req,
history_details
]
)
.attach(Template::fairing()) .attach(Template::fairing())
.launch(); .launch();
} }

View File

@ -8,32 +8,48 @@ use crate::schema::{
}; };
use diesel::pg::Pg; use diesel::pg::Pg;
/// Client Record that keeps track of unique clients that have connected to this application
/// These are used to keep track of who submitted what request that will be recorded
#[derive(Queryable, Selectable, Debug)] #[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = clients)] #[diesel(table_name = clients)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct Client { pub struct Client {
pub id: i32, pub id: i64,
pub ip: String, pub ip: String,
pub mirror_id: String, pub client_id: String,
} }
/// Used to create a new Client Record
#[derive(Insertable)] #[derive(Insertable)]
#[diesel(table_name = clients)] #[diesel(table_name = clients)]
pub struct NewClient<'a> { pub struct NewClient<'a> {
pub ip: &'a str, pub ip: &'a str,
pub mirror_id: &'a str, pub client_id: &'a str,
} }
/// History record that keeps track of each unique request that comes in to the /test route
/// Any request is recorded here and the additional attributes are stored as a Pair Record
#[derive(Queryable, Selectable, Debug)] #[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = history)] #[diesel(table_name = history)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct HistoryRecord { pub struct HistoryRecord {
pub id: i32, pub id: i64,
pub client_id: String, pub client_id: String,
pub request_type: String, pub request_type: String,
pub timestamp: NaiveDateTime pub timestamp: NaiveDateTime
} }
/// History Struct used to represent HistoryRecord in a serializable way.
/// This is intended to allow an instance to be used with a Handlebars Template
#[derive(Serialize)]
pub struct History {
pub id: i64,
pub client_id: String,
pub request_type: String,
pub timestamp: String
}
/// Used to create a new History Record
#[derive(Insertable)] #[derive(Insertable)]
#[diesel(table_name = history)] #[diesel(table_name = history)]
pub struct NewHistoryRecord<'a> { pub struct NewHistoryRecord<'a> {
@ -42,22 +58,50 @@ pub struct NewHistoryRecord<'a> {
pub timestamp: NaiveDateTime pub timestamp: NaiveDateTime
} }
/// Pair Records are used to store attributes of a request. This includes a body record, header, query and cookie record.
/// Each has a key and value.
#[derive(Queryable, Selectable, Debug, Serialize)] #[derive(Queryable, Selectable, Debug, Serialize)]
#[diesel(table_name = pair_records)] #[diesel(table_name = pair_records)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct PairRecord { pub struct PairRecord {
pub id: i32, pub id: i64,
pub history_id: i32, pub history_id: i64,
pub pair_type: i32, pub pair_type: i16,
pub key: String, pub key: String,
pub value: String pub value: String
} }
/// Used to create a new Pair Record
#[derive(Insertable)] #[derive(Insertable)]
#[diesel(table_name = pair_records)] #[diesel(table_name = pair_records)]
pub struct NewPairRecord<'a> { pub struct NewPairRecord<'a> {
pub history_id: i32, pub history_id: i64,
pub pair_type: i32, pub pair_type: i16,
pub key: &'a str, pub key: &'a str,
pub value: &'a str pub value: &'a str
}
/// Used to indicate the type of pair a Pair Record may be.
#[derive(Serialize, Debug, Clone)]
pub enum PairType {
Header,
Cookie,
Query,
Body
}
/// Used to store a pair of key values
/// This structure is used for storing different values in the database and
/// rendering mustache templates with an array of key value pairs.
#[derive(Serialize, Debug, Clone)]
pub struct Pair {
pub key: String,
pub value: String
}
/// Used when needing to return a template with an error message.
/// Specify error_msg to display your custom message.
#[derive(Serialize)]
pub struct ErrorContext {
pub error_msg: String
} }

View File

@ -2,15 +2,15 @@
diesel::table! { diesel::table! {
clients (id) { clients (id) {
id -> Int4, id -> Int8,
ip -> Text, ip -> Text,
mirror_id -> Text, client_id -> Text,
} }
} }
diesel::table! { diesel::table! {
history (id) { history (id) {
id -> Int4, id -> Int8,
client_id -> Text, client_id -> Text,
request_type -> Text, request_type -> Text,
timestamp -> Timestamp, timestamp -> Timestamp,
@ -19,7 +19,7 @@ diesel::table! {
diesel::table! { diesel::table! {
ownership (id) { ownership (id) {
id -> Int4, id -> Int8,
owner_id -> Text, owner_id -> Text,
client_id -> Text, client_id -> Text,
} }
@ -27,9 +27,9 @@ diesel::table! {
diesel::table! { diesel::table! {
pair_records (id) { pair_records (id) {
id -> Int4, id -> Int8,
history_id -> Int4, history_id -> Int8,
pair_type -> Int4, pair_type -> Int2,
key -> Text, key -> Text,
value -> Text, value -> Text,
} }