diff --git a/Cargo.toml b/Cargo.toml index e08aee1..525a06b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,4 @@ toml = "0.8.19" url-escape = "0.1.1" base64 = "0.22.1" sha2 = "0.10.8" -itertools = "0.14.0" +itertools = "0.14.0" \ No newline at end of file diff --git a/src/archive/fragment.rs b/src/archive/fragment.rs index c7589c4..9a8c0f7 100644 --- a/src/archive/fragment.rs +++ b/src/archive/fragment.rs @@ -35,3 +35,14 @@ pub async fn get_fragments_of_domain(domain: &str) -> Vec<(String, String)> { res.into_iter().map(|x| (x.0, x.1)).collect() } + +pub async fn get_domains_of_fragment(fragment: &str) -> Vec<(String, String, chrono::NaiveDate)> { + let res: Vec<(String, String, chrono::NaiveDate)> = + sqlx::query_as("SELECT domain, path, version FROM document_fragments WHERE fragment = $1") + .bind(fragment) + .fetch_all(get_pg!()) + .await + .unwrap(); + + res.into_iter().map(|x| (x.0, x.1, x.2)).collect() +} diff --git a/src/main.rs b/src/main.rs index 5d5ff30..1349c44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,8 @@ async fn main() { pages::fragment_get, pages::mime_overview, pages::fragments_overview, - pages::fragments_domain_overview + pages::fragments_domain_overview, + pages::fragment_overview ], ) .manage(arc) diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 8422291..6deb228 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, io::Read, path::PathBuf, sync::Arc}; use based::ui::components::prelude::*; use based::ui::prelude::*; +use based::ui::primitives::div::Center; use based::{ page, request::{ @@ -14,6 +15,7 @@ use based::{ use chrono::NaiveDate; use itertools::Itertools; use maud::{html, PreEscaped}; +use regex::Regex; use rocket::response::Redirect; use rocket::{get, request::FromSegments, State}; @@ -22,8 +24,8 @@ use component::*; use serde_json::json; use webarc::archive::{ - domain_has_fragments, get_fragment, get_fragments_of_domain, get_random_fragment_id, - internalize_urls, Document, DocumentIndex, + domain_has_fragments, get_domains_of_fragment, get_fragment, get_fragments_of_domain, + get_random_fragment_id, internalize_urls, Document, DocumentIndex, }; use webarc::get_mime_type; use webarc::{ @@ -448,6 +450,17 @@ pub async fn render_website( .as_bytes() .to_vec(); } + + // TODO : Fix + // This will work if the `Content-Security-Policy` meta tag gets removed from the document + // -> Migrate to HTML parser + + /* + content = replace_data_urls( + &String::from_utf8_lossy(&content), + "http://127.0.0.1:8000" + ).as_bytes().to_vec(); + */ } return Some(DataResponse::new(content, mime, Some(60 * 60 * 24))); @@ -458,6 +471,24 @@ pub async fn render_website( None } +pub fn replace_data_urls(input: &str, root: &str) -> String { + let data_url_pattern = r#"data:([a-zA-Z0-9]+/[a-zA-Z0-9.+-]+);base64,([a-zA-Z0-9+/=]+)"#; + let re_data = Regex::new(data_url_pattern).unwrap(); + + re_data + .replace_all(input, |caps: ®ex::Captures| { + let encoded_data = caps.get(2).unwrap().as_str(); + + if let Ok(decoded) = base64::decode(encoded_data) { + let hash = webarc::sha256_hash(&decoded); + format!("{root}/f/{hash}") + } else { + caps[0].to_string() + } + }) + .to_string() +} + pub fn gen_search_element(x: &SearchResult) -> PreEscaped { html! { div class="text-xl font-bold mt-4 p-4 flex items-center w-full max-w-4xl max-h-40 mx-auto bg-neutral-800 shadow-md rounded-lg overflow-hidden border border-neutral-900 hover:cursor-pointer" @@ -646,6 +677,60 @@ pub async fn mime_overview( ) } +#[get("/fragment/")] +pub async fn fragment_overview( + fragment: &str, + ctx: RequestContext, + shell: &State, +) -> Option { + let fragment_info = get_fragment(fragment).await?; + + let docs = get_domains_of_fragment(fragment).await; + + let content = Margin( + Column(vec![ + Center(Card( + Row(vec![ + Text(fragment).render(), + Rounded( + Padding( + Background(Text(&fragment_info.1).bold().black().xs()) + .color(Colors::White), + ) + .all(ScreenValue::_2), + ) + .render(), + Link(&format!("/f/{fragment}"), Button("Raw")).render(), + ]) + .gap(ScreenValue::_4) + .items_center(), + )) + .render(), + Center(Card( + UnorderedList() + .push(Text("Referenced on:").bold()._2xl()) + .push_for_each(&docs, |(domain, path, _): &_| { + Link( + &format!("/d/{domain}/{path}"), + Row(vec![ + favicon(domain), + Text(domain).render(), + Text(path).render(), + ]) + .items_center(), + ) + }), + )) + .render(), + ]) + .gap(ScreenValue::_4), + ) + .top(ScreenValue::_4) + .render(); + + Some(page!(shell, ctx, "Fragment", content)) +} + #[get("/fragments/")] pub async fn fragments_domain_overview( domain: &str, @@ -659,12 +744,15 @@ pub async fn fragments_domain_overview( .into_iter() .map(|(fragment, mime)| { if mime.starts_with("image") { - return Card(Image(&format!("/f/{fragment}")).height(128).width(128)); + return Card(Link( + &format!("/fragment/{fragment}"), + Image(&format!("/f/{fragment}")).height(128).width(128), + )); } Card( Tooltip( - Link(&format!("/f/{fragment}"), Text(&fragment)), + Link(&format!("/fragment/{fragment}"), Text(&fragment)), Text(&mime), ) .white(),