Skip to content

Commit

Permalink
add meta to index.html paste on paste id
Browse files Browse the repository at this point in the history
  • Loading branch information
merlinfuchs committed Mar 26, 2022
1 parent 40c6fae commit 9c2a849
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 48 deletions.
339 changes: 324 additions & 15 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ base64 = "0.13.0"
sha2 = "0.10.2"
thiserror = "1.0.30"
rust-embed = { version = "6.3.0", features = ["interpolate-folder-path"] }
lol_html = "0.3.1"
html-escape = "0.2.11"
unicode-truncate = "0.2.0"

[features]
cors = ["actix-cors"]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ To build this project from source you need [node](https://nodejs.org/en/download

Build the frontend:
```shell
npm run install
npm run build
```

Expand Down
1 change: 1 addition & 0 deletions frontend/src/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>vaultbin</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta property="og:title" content="vaultbin">
<% preact.headEnd %>
</head>
<body>
Expand Down
9 changes: 9 additions & 0 deletions src/database/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use base64::{CharacterSet, DecodeError};
use sha2::{Digest, Sha256};

use encryption::*;
Expand All @@ -16,6 +17,14 @@ pub struct Database {
db: sled::Db,
}

pub fn encode_bytes_to_string(bytes: &[u8]) -> String {
base64::encode_config(bytes, base64::Config::new(CharacterSet::UrlSafe, false))
}

pub fn decode_bytes_from_string(string: &str) -> Result<Vec<u8>, DecodeError> {
base64::decode_config(string, base64::Config::new(CharacterSet::UrlSafe, false))
}

fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
let number = match old {
Some(bytes) => {
Expand Down
7 changes: 3 additions & 4 deletions src/routes/api_paste_create.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use actix_web::{HttpResponse, post, Responder, web};
use base64::CharacterSet;

use crate::database::{Database, PasteModel};
use crate::database::{Database, encode_bytes_to_string, PasteModel};
use crate::VaultbinConfig;
use crate::wire::{ApiError, ApiResponse, CreatePasteRequestData, CreatePasteResponseData, PasteResponseData, RouteResult};

Expand All @@ -12,7 +11,7 @@ pub async fn route_api_paste_create(db: web::Data<Database>, data: web::Json<Cre
let data = data.into_inner();

if data.content.len() > config.max_paste_size {
return Err(ApiError::PasteTooLarge)
return Err(ApiError::PasteTooLarge);
}

let model = PasteModel {
Expand All @@ -25,7 +24,7 @@ pub async fn route_api_paste_create(db: web::Data<Database>, data: web::Json<Cre
let expiry = data.expiration.unwrap_or(config.max_expiration).min(config.max_expiration);
db.set_paste_expiration(&id, Duration::from_secs(expiry))?;

let encoded_id = base64::encode_config(id, base64::Config::new(CharacterSet::UrlSafe, false));
let encoded_id = encode_bytes_to_string(&id);
let paste = PasteResponseData {
id: encoded_id,
content: model.content,
Expand Down
9 changes: 4 additions & 5 deletions src/routes/api_paste_get.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use actix_web::{get, HttpResponse, Responder, web};
use base64::CharacterSet;

use crate::database::Database;
use crate::database::{Database, decode_bytes_from_string};
use crate::wire::{ApiError, ApiResponse, GetPasteResponseData, PasteResponseData, RouteResult};

#[get("/api/pastes/{paste_id}")]
pub async fn route_api_paste_get(db: web::Data<Database>, paste_id: web::Path<String>) -> RouteResult<impl Responder> {
let paste_id = paste_id.into_inner();
let decode_paste_id = base64::decode_config(&paste_id, base64::Config::new(CharacterSet::UrlSafe, false))?;
let decoded_paste_id = decode_bytes_from_string(&paste_id)?;

let model = db.get_paste(&decode_paste_id)?
let model = db.get_paste(&decoded_paste_id)?
.ok_or(ApiError::UnknownPaste)?;

let view_count = db.count_paste_view(&decode_paste_id)?;
let view_count = db.count_paste_view(&decoded_paste_id)?;

let paste = PasteResponseData {
id: paste_id,
Expand Down
10 changes: 4 additions & 6 deletions src/routes/api_paste_get_raw.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
use actix_web::{get, HttpResponse, Responder, web};
use base64::CharacterSet;

use crate::database::{Database, decode_bytes_from_string};
use crate::wire::{ApiError, RouteResult};

use crate::database::Database;

#[get("/api/pastes/{paste_id}/raw")]
pub async fn route_api_paste_get_raw(db: web::Data<Database>, paste_id: web::Path<String>) -> RouteResult<impl Responder> {
let paste_id = paste_id.into_inner();
let decode_paste_id = base64::decode_config(&paste_id, base64::Config::new(CharacterSet::UrlSafe, false))?;
let model = db.get_paste(&decode_paste_id)?
let decoded_paste_id = decode_bytes_from_string(&paste_id)?;
let model = db.get_paste(&decoded_paste_id)?
.ok_or(ApiError::UnknownPaste)?;

db.count_paste_view(&decode_paste_id)?;
db.count_paste_view(&decoded_paste_id)?;
Ok(HttpResponse::Ok()
.append_header(("Content-Type", "text/plain"))
.body(model.content))
Expand Down
96 changes: 78 additions & 18 deletions src/routes/serve_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ use std::path::Path;

use actix_web::{get, HttpResponse, Responder, web};
use actix_web::web::Bytes;
use lol_html::{element, HtmlRewriter};
use lol_html::html_content::ContentType;
use mime::Mime;
use rust_embed::RustEmbed;
use unicode_truncate::UnicodeTruncateStr;

use crate::database::{Database, decode_bytes_from_string};

#[derive(RustEmbed)]
#[folder = "$CARGO_MANIFEST_DIR/frontend/build"]
Expand All @@ -24,25 +29,80 @@ fn get_mime_type_for_file(path: &Path) -> Mime {
}
}

#[get("/{path:.*}")]
pub async fn route_serve_frontend(path: web::Path<String>) -> impl Responder {
let mut path = path.into_inner();
let mut file = FrontendFiles::get(&path);
if file.is_none() {
path = String::from("index.html");
file = FrontendFiles::get(&path);
fn cow_to_bytes(cow: Cow<'static, [u8]>) -> Bytes {
match cow {
Cow::Borrowed(bytes) => bytes.into(),
Cow::Owned(bytes) => bytes.into(),
}
}

// panics when there is no index.html
fn get_index_file(path: &Path, db: &Database) -> Bytes {
let maybe_paste_id = match path.file_name() {
Some(n) if n.len() > 16 && n.len() < 32 => n.to_str(),
_ => None
};

let fallback = || cow_to_bytes(FrontendFiles::get("index.html").unwrap().data);

let paste_id = match maybe_paste_id {
Some(p) => p,
None => return fallback()
};

let decode_paste_id = match decode_bytes_from_string(&paste_id) {
Ok(p) => p,
Err(_) => return fallback()
};

let paste = match db.get_paste(&decode_paste_id) {
Ok(Some(p)) => p,
_ => return fallback()
};

if let Some(file) = file {
let mime_type = get_mime_type_for_file(Path::new(&path));
let body: Bytes = match file.data {
Cow::Borrowed(bytes) => bytes.into(),
Cow::Owned(bytes) => bytes.into(),
};
HttpResponse::Ok()
.append_header(("Content-Type", mime_type))
.body(body)
} else {
HttpResponse::NotFound().finish()
let file = FrontendFiles::get("index.html").unwrap();
let mut output = Vec::with_capacity(file.data.len());

let mut rewriter = HtmlRewriter::new(
lol_html::Settings {
element_content_handlers: vec![
element!("head", |el| {
let escaped_content = html_escape::encode_text(paste.content.unicode_truncate(500).0);
let description_meta = format!("<meta property=\"og:description\" content=\"{}\"/>", escaped_content);
el.append(&description_meta, ContentType::Html);
/* if let Some(language) = &paste.language {
let image_meta = format!("<meta property=\"og:image\" property=\"https://cdn.jsdelivr.net/npm/programming-languages-logos@0.0.3/src/{}/{}.png\"/>", language, language);
el.append(&image_meta, ContentType::Html);
} */
Ok(())
})
],
..lol_html::Settings::default()
},
|c: &[u8]| output.extend_from_slice(c),
);

match rewriter.write(file.data.as_ref()) {
Ok(_) => output.into(),
// we really don't want to fail here
Err(_) => cow_to_bytes(file.data)
}
}

#[get("/{path:.*}")]
pub async fn route_serve_frontend(path: web::Path<String>, db: web::Data<Database>) -> impl Responder {
let raw_path = path.into_inner();
let path = Path::new(&raw_path);

let (body, mime_type) = match raw_path.as_str() {
"index.html" => (get_index_file(&path, &db), mime::TEXT_HTML),
p => match FrontendFiles::get(p) {
Some(f) => (cow_to_bytes(f.data), get_mime_type_for_file(&path)),
None => (get_index_file(&path, &db), mime::TEXT_HTML)
}
};

HttpResponse::Ok()
.append_header(("Content-Type", mime_type))
.body(body)
}

0 comments on commit 9c2a849

Please sign in to comment.