Merge branch 'actix'
This commit is contained in:
commit
e6642a7a94
13 changed files with 1868 additions and 1259 deletions
2571
Cargo.lock
generated
2571
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
29
Cargo.toml
29
Cargo.toml
|
@ -1,24 +1,31 @@
|
|||
[package]
|
||||
name = "bin"
|
||||
version = "1.0.5"
|
||||
version = "2.0.0"
|
||||
description = "a paste bin."
|
||||
repository = "https://github.com/w4/bin"
|
||||
license = "WTFPL OR 0BSD"
|
||||
authors = ["Jordan Doyle <jordan@doyle.la>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
owning_ref = "0.4"
|
||||
argh = "0.1"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
linked-hash-map = "0.5"
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master" }
|
||||
askama = "0.9"
|
||||
lazy_static = "1.4"
|
||||
rand = { version = "0.7", features = ["nightly"] }
|
||||
once_cell = "1.10"
|
||||
parking_lot = "0.12"
|
||||
bytes = { version = "1.1", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rand = { version = "0.8" }
|
||||
gpw = "0.1"
|
||||
syntect = "4.1"
|
||||
serde_derive = "1.0"
|
||||
tokio = { version = "0.2", features = ["sync", "macros"] }
|
||||
async-trait = "0.1"
|
||||
actix = "0.13"
|
||||
actix-web = "4.0"
|
||||
htmlescape = "0.3"
|
||||
askama = "0.11"
|
||||
bat = "0.20"
|
||||
syntect = "4.6"
|
||||
tokio = { version = "1.17", features = ["sync"] }
|
||||
futures = "0.3"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
FROM rust:1.34.2-slim-stretch AS builder
|
||||
RUN rustup install nightly-x86_64-unknown-linux-gnu
|
||||
FROM rust:1-slim AS builder
|
||||
|
||||
RUN apt update && apt install -y libclang-dev
|
||||
|
||||
COPY . /sources
|
||||
WORKDIR /sources
|
||||
RUN cargo +nightly build --release
|
||||
RUN cargo build --release
|
||||
RUN chown nobody:nogroup /sources/target/release/bin
|
||||
|
||||
|
||||
FROM debian:stretch-slim
|
||||
FROM debian:bullseye-slim
|
||||
COPY --from=builder /sources/target/release/bin /pastebin
|
||||
|
||||
USER nobody
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT ["/pastebin"]
|
||||
ENTRYPOINT ["/pastebin", "0.0.0.0:8000"]
|
||||
|
|
19
README.md
19
README.md
|
@ -3,7 +3,7 @@ a paste bin.
|
|||
|
||||
A paste bin that's actually minimalist. No database requirement, no commenting functionality, no self-destructing or time bomb messages and no social media integration—just an application to quickly send snippets of text to people.
|
||||
|
||||
[bin](https://bin.gy/) is written in Rust in around 200 lines of code. It's fast, it's simple, there's code highlighting and you can ⌘+A without going to the 'plain' page. It's revolutionary in the paste bin industry, disrupting markets and pushing boundaries never seen before.
|
||||
[bin](https://bin.gy/) is written in Rust in around 300 lines of code. It's fast, it's simple, there's code highlighting and you can ⌘+A without going to the 'plain' page. It's revolutionary in the paste bin industry, disrupting markets and pushing boundaries never seen before.
|
||||
|
||||
##### so how do you get bin?
|
||||
|
||||
|
@ -29,9 +29,22 @@ $ ./bin
|
|||
|
||||
##### funny, what settings are there?
|
||||
|
||||
bin uses [rocket](https://rocket.rs) so you can add a [rocket config file](https://api.rocket.rs/v0.3/rocket/config/) if you like. You can set `ROCKET_PORT` in your environment if you want to change the default port (8820).
|
||||
```
|
||||
$ ./bin
|
||||
|
||||
bin's only configuration value is `BIN_BUFFER_SIZE` which defaults to 2000. Change this value if you want your bin to hold more pastes.
|
||||
Usage: bin [<bind_addr>] [--buffer-size <buffer-size>] [--max-paste-size <max-paste-size>]
|
||||
|
||||
a pastebin.
|
||||
|
||||
Positional Arguments:
|
||||
bind_addr socket address to bind to (default: 127.0.0.1:8820)
|
||||
|
||||
Options:
|
||||
--buffer-size maximum amount of pastes to store before rotating (default:
|
||||
1000)
|
||||
--max-paste-size maximum paste size in bytes (default. 32kB)
|
||||
--help display usage information
|
||||
```
|
||||
|
||||
##### is there curl support?
|
||||
|
||||
|
|
56
src/errors.rs
Normal file
56
src/errors.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use actix_web::{body::BoxBody, http::header, http::StatusCode, web, HttpResponse, ResponseError};
|
||||
|
||||
use std::fmt::{Formatter, Write};
|
||||
|
||||
macro_rules! impl_response_error_for_http_resp {
|
||||
($ty:ty, $path:expr, $status:expr) => {
|
||||
impl ResponseError for $ty {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HtmlResponseError::error_response(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $ty {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", include_str!($path))
|
||||
}
|
||||
}
|
||||
|
||||
impl HtmlResponseError for $ty {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
$status
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotFound;
|
||||
|
||||
impl_response_error_for_http_resp!(NotFound, "../templates/404.html", StatusCode::NOT_FOUND);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InternalServerError(pub Box<dyn std::error::Error>);
|
||||
|
||||
impl_response_error_for_http_resp!(
|
||||
InternalServerError,
|
||||
"../templates/500.html",
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
);
|
||||
|
||||
pub trait HtmlResponseError: ResponseError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let mut resp = HttpResponse::new(HtmlResponseError::status_code(self));
|
||||
let mut buf = web::BytesMut::new();
|
||||
let _ = write!(&mut buf, "{}", self);
|
||||
resp.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/html; charset=utf-8"),
|
||||
);
|
||||
resp.set_body(BoxBody::new(buf))
|
||||
}
|
||||
}
|
|
@ -1,26 +1,45 @@
|
|||
extern crate syntect;
|
||||
use bat::assets::HighlightingAssets;
|
||||
use once_cell::sync::Lazy;
|
||||
use syntect::{
|
||||
html::{ClassStyle, ClassedHTMLGenerator},
|
||||
parsing::SyntaxSet,
|
||||
};
|
||||
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
thread_local!(pub static BAT_ASSETS: HighlightingAssets = HighlightingAssets::from_binary());
|
||||
|
||||
/// Takes the content of a paste and the extension passed in by the viewer and will return the content
|
||||
/// highlighted in the appropriate format in HTML.
|
||||
///
|
||||
/// Returns `None` if the extension isn't supported.
|
||||
pub fn highlight(content: &str, ext: &str) -> Option<String> {
|
||||
lazy_static! {
|
||||
static ref SS: SyntaxSet = SyntaxSet::load_defaults_newlines();
|
||||
static ref TS: ThemeSet = ThemeSet::load_defaults();
|
||||
}
|
||||
static SS: Lazy<SyntaxSet> = Lazy::new(SyntaxSet::load_defaults_newlines);
|
||||
|
||||
let syntax = SS.find_syntax_by_extension(ext)?;
|
||||
let mut h = HighlightLines::new(syntax, &TS.themes["base16-ocean.dark"]);
|
||||
let regions = h.highlight(content, &SS);
|
||||
|
||||
Some(styled_line_to_highlighted_html(
|
||||
®ions[..],
|
||||
IncludeBackground::No,
|
||||
))
|
||||
BAT_ASSETS.with(|f| {
|
||||
let ss = f.get_syntax_set().ok().unwrap_or(&SS);
|
||||
let syntax = ss.find_syntax_by_extension(ext)?;
|
||||
let mut html_generator =
|
||||
ClassedHTMLGenerator::new_with_class_style(syntax, ss, ClassStyle::Spaced);
|
||||
for line in LinesWithEndings(content.trim()) {
|
||||
html_generator.parse_html_for_line_which_includes_newline(line);
|
||||
}
|
||||
Some(html_generator.finalize())
|
||||
})
|
||||
}
|
||||
|
||||
pub struct LinesWithEndings<'a>(&'a str);
|
||||
|
||||
impl<'a> Iterator for LinesWithEndings<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.0.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let split = self.0.find('\n').map_or(self.0.len(), |i| i + 1);
|
||||
let (line, rest) = self.0.split_at(split);
|
||||
self.0 = rest;
|
||||
Some(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
62
src/io.rs
62
src/io.rs
|
@ -1,42 +1,21 @@
|
|||
extern crate gpw;
|
||||
extern crate linked_hash_map;
|
||||
extern crate owning_ref;
|
||||
extern crate rand;
|
||||
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use actix_web::web::Bytes;
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
|
||||
use owning_ref::OwningRef;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
pub type PasteStore = RwLock<LinkedHashMap<String, Bytes>>;
|
||||
|
||||
type RwLockReadGuardRef<'a, T, U = T> = OwningRef<Box<RwLockReadGuard<'a, T>>, U>;
|
||||
|
||||
lazy_static! {
|
||||
static ref ENTRIES: RwLock<LinkedHashMap<String, String>> = RwLock::new(LinkedHashMap::new());
|
||||
static ref BUFFER_SIZE: usize = env::var("BIN_BUFFER_SIZE")
|
||||
.map(|f| f
|
||||
.parse::<usize>()
|
||||
.expect("Failed to parse value of BIN_BUFFER_SIZE"))
|
||||
.unwrap_or(1000usize);
|
||||
}
|
||||
static BUFFER_SIZE: Lazy<usize> = Lazy::new(|| argh::from_env::<crate::BinArgs>().buffer_size);
|
||||
|
||||
/// Ensures `ENTRIES` is less than the size of `BIN_BUFFER_SIZE`. If it isn't then
|
||||
/// `ENTRIES.len() - BIN_BUFFER_SIZE` elements will be popped off the front of the map.
|
||||
///
|
||||
/// During the purge, `ENTRIES` is locked and the current thread will block.
|
||||
async fn purge_old() {
|
||||
let entries_len = ENTRIES.read().await.len();
|
||||
|
||||
if entries_len > *BUFFER_SIZE {
|
||||
let to_remove = entries_len - *BUFFER_SIZE;
|
||||
|
||||
let mut entries = ENTRIES.write().await;
|
||||
fn purge_old(entries: &mut LinkedHashMap<String, Bytes>) {
|
||||
if entries.len() > *BUFFER_SIZE {
|
||||
let to_remove = entries.len() - *BUFFER_SIZE;
|
||||
|
||||
for _ in 0..to_remove {
|
||||
entries.pop_front();
|
||||
|
@ -52,29 +31,24 @@ pub fn generate_id() -> String {
|
|||
thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(6)
|
||||
.collect::<String>()
|
||||
.map(char::from)
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// Stores a paste under the given id
|
||||
pub async fn store_paste(id: String, content: String) {
|
||||
purge_old().await;
|
||||
pub fn store_paste(entries: &PasteStore, id: String, content: Bytes) {
|
||||
let mut entries = entries.write();
|
||||
|
||||
ENTRIES.write().await.insert(id, content);
|
||||
purge_old(&mut entries);
|
||||
|
||||
entries.insert(id, content);
|
||||
}
|
||||
|
||||
/// Get a paste by id.
|
||||
///
|
||||
/// Returns `None` if the paste doesn't exist.
|
||||
pub async fn get_paste(
|
||||
id: &str,
|
||||
) -> Option<RwLockReadGuardRef<'_, LinkedHashMap<String, String>, String>> {
|
||||
pub fn get_paste(entries: &PasteStore, id: &str) -> Option<Bytes> {
|
||||
// need to box the guard until owning_ref understands Pin is a stable address
|
||||
let or = RwLockReadGuardRef::new(Box::new(ENTRIES.read().await));
|
||||
|
||||
if or.contains_key(id) {
|
||||
Some(or.map(|x| x.get(id).unwrap()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
entries.read().get(id).map(Bytes::clone)
|
||||
}
|
||||
|
|
221
src/main.rs
221
src/main.rs
|
@ -1,140 +1,193 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
extern crate askama;
|
||||
#![deny(clippy::pedantic)]
|
||||
#![allow(clippy::unused_async)]
|
||||
|
||||
mod errors;
|
||||
mod highlight;
|
||||
mod io;
|
||||
mod params;
|
||||
|
||||
use highlight::highlight;
|
||||
use io::{generate_id, get_paste, store_paste};
|
||||
use params::{HostHeader, IsPlaintextRequest};
|
||||
use crate::{
|
||||
errors::{InternalServerError, NotFound},
|
||||
highlight::highlight,
|
||||
io::{generate_id, get_paste, store_paste, PasteStore},
|
||||
params::{HostHeader, IsPlaintextRequest},
|
||||
};
|
||||
|
||||
use actix_web::{
|
||||
http::header,
|
||||
web::{self, Bytes, Data, FormConfig, PayloadConfig},
|
||||
App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
use askama::{Html as AskamaHtml, MarkupDisplay, Template};
|
||||
use log::{error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
};
|
||||
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
|
||||
|
||||
use rocket::http::{ContentType, RawStr, Status};
|
||||
use rocket::request::Form;
|
||||
use rocket::response::content::{Content, Html};
|
||||
use rocket::response::Redirect;
|
||||
use rocket::Data;
|
||||
#[derive(argh::FromArgs, Clone)]
|
||||
/// a pastebin.
|
||||
pub struct BinArgs {
|
||||
/// socket address to bind to (default: 127.0.0.1:8820)
|
||||
#[argh(
|
||||
positional,
|
||||
default = "SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8820)"
|
||||
)]
|
||||
bind_addr: SocketAddr,
|
||||
/// maximum amount of pastes to store before rotating (default: 1000)
|
||||
#[argh(option, default = "1000")]
|
||||
buffer_size: usize,
|
||||
/// maximum paste size in bytes (default. 32kB)
|
||||
#[argh(option, default = "32 * 1024")]
|
||||
max_paste_size: usize,
|
||||
}
|
||||
|
||||
use std::borrow::Cow;
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
if std::env::var_os("RUST_LOG").is_none() {
|
||||
std::env::set_var("RUST_LOG", "INFO");
|
||||
}
|
||||
pretty_env_logger::init();
|
||||
|
||||
use tokio::io::AsyncReadExt;
|
||||
let args: BinArgs = argh::from_env();
|
||||
|
||||
///
|
||||
/// Homepage
|
||||
///
|
||||
let store = Data::new(PasteStore::default());
|
||||
|
||||
let server = HttpServer::new({
|
||||
let args = args.clone();
|
||||
|
||||
move || {
|
||||
App::new()
|
||||
.app_data(store.clone())
|
||||
.app_data(PayloadConfig::default().limit(args.max_paste_size))
|
||||
.app_data(FormConfig::default().limit(args.max_paste_size))
|
||||
.wrap(actix_web::middleware::Compress::default())
|
||||
.route("/", web::get().to(index))
|
||||
.route("/", web::post().to(submit))
|
||||
.route("/", web::put().to(submit_raw))
|
||||
.route("/", web::head().to(HttpResponse::MethodNotAllowed))
|
||||
.route("/highlight.css", web::get().to(highlight_css))
|
||||
.route("/{paste}", web::get().to(show_paste))
|
||||
.route("/{paste}", web::head().to(HttpResponse::MethodNotAllowed))
|
||||
.default_service(web::to(|req: HttpRequest| async move {
|
||||
error!("Couldn't find resource {}", req.uri());
|
||||
HttpResponse::from_error(NotFound)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
info!("Listening on http://{}", args.bind_addr);
|
||||
|
||||
server.bind(args.bind_addr)?.run().await
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct Index;
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> Result<Html<String>, Status> {
|
||||
Index
|
||||
.render()
|
||||
.map(Html)
|
||||
.map_err(|_| Status::InternalServerError)
|
||||
async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
render_template(&req, &Index)
|
||||
}
|
||||
|
||||
///
|
||||
/// Submit Paste
|
||||
///
|
||||
|
||||
#[derive(FromForm)]
|
||||
#[derive(serde::Deserialize)]
|
||||
struct IndexForm {
|
||||
val: String,
|
||||
val: Bytes,
|
||||
}
|
||||
|
||||
#[post("/", data = "<input>")]
|
||||
async fn submit(input: Form<IndexForm>) -> Redirect {
|
||||
async fn submit(input: web::Form<IndexForm>, store: Data<PasteStore>) -> impl Responder {
|
||||
let id = generate_id();
|
||||
let uri = uri!(show_paste: &id);
|
||||
store_paste(id, input.into_inner().val).await;
|
||||
Redirect::to(uri)
|
||||
let uri = format!("/{}", &id);
|
||||
store_paste(&store, id, input.into_inner().val);
|
||||
HttpResponse::Found()
|
||||
.append_header((header::LOCATION, uri))
|
||||
.finish()
|
||||
}
|
||||
|
||||
#[put("/", data = "<input>")]
|
||||
async fn submit_raw(input: Data, host: HostHeader<'_>) -> Result<String, Status> {
|
||||
let mut data = String::new();
|
||||
input
|
||||
.open()
|
||||
.take(1024 * 1000)
|
||||
.read_to_string(&mut data)
|
||||
.await
|
||||
.map_err(|_| Status::InternalServerError)?;
|
||||
|
||||
async fn submit_raw(
|
||||
data: Bytes,
|
||||
host: HostHeader,
|
||||
store: Data<PasteStore>,
|
||||
) -> Result<String, Error> {
|
||||
let id = generate_id();
|
||||
let uri = uri!(show_paste: &id);
|
||||
let uri = if let Some(Ok(host)) = host.0.as_ref().map(|v| std::str::from_utf8(v.as_bytes())) {
|
||||
format!("https://{}/{}", host, id)
|
||||
} else {
|
||||
format!("/{}", id)
|
||||
};
|
||||
|
||||
store_paste(id, data).await;
|
||||
store_paste(&store, id, data);
|
||||
|
||||
match *host {
|
||||
Some(host) => Ok(format!("https://{}{}", host, uri)),
|
||||
None => Ok(format!("{}", uri)),
|
||||
}
|
||||
Ok(uri)
|
||||
}
|
||||
|
||||
///
|
||||
/// Show paste page
|
||||
///
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "paste.html")]
|
||||
struct ShowPaste<'a> {
|
||||
content: MarkupDisplay<AskamaHtml, Cow<'a, String>>,
|
||||
}
|
||||
|
||||
#[get("/<key>")]
|
||||
async fn show_paste(key: String, plaintext: IsPlaintextRequest) -> Result<Content<String>, Status> {
|
||||
async fn show_paste(
|
||||
req: HttpRequest,
|
||||
key: actix_web::web::Path<String>,
|
||||
plaintext: IsPlaintextRequest,
|
||||
store: Data<PasteStore>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let mut splitter = key.splitn(2, '.');
|
||||
let key = splitter.next().ok_or_else(|| Status::NotFound)?;
|
||||
let key = splitter.next().unwrap();
|
||||
let ext = splitter.next();
|
||||
|
||||
let entry = &*get_paste(key).await.ok_or_else(|| Status::NotFound)?;
|
||||
let entry = get_paste(&store, key).ok_or(NotFound)?;
|
||||
|
||||
if *plaintext {
|
||||
Ok(Content(ContentType::Plain, entry.to_string()))
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(entry))
|
||||
} else {
|
||||
let data = std::str::from_utf8(entry.as_ref())?;
|
||||
|
||||
let code_highlighted = match ext {
|
||||
Some(extension) => match highlight(&entry, extension) {
|
||||
Some(extension) => match highlight(data, extension) {
|
||||
Some(html) => html,
|
||||
None => return Err(Status::NotFound),
|
||||
None => return Err(NotFound.into()),
|
||||
},
|
||||
None => String::from(RawStr::from_str(entry).html_escape()),
|
||||
None => htmlescape::encode_minimal(data),
|
||||
};
|
||||
|
||||
// Add <code> tags to enable line numbering with CSS
|
||||
let html = format!(
|
||||
"<code>{}</code>",
|
||||
code_highlighted.replace("\n", "\n</code><code>")
|
||||
code_highlighted.replace('\n', "</code><code>")
|
||||
);
|
||||
|
||||
let content = MarkupDisplay::new_safe(Cow::Borrowed(&html), AskamaHtml);
|
||||
|
||||
let template = ShowPaste { content };
|
||||
match template.render() {
|
||||
Ok(html) => Ok(Content(ContentType::HTML, html)),
|
||||
Err(_) => Err(Status::InternalServerError),
|
||||
render_template(&req, &ShowPaste { content })
|
||||
}
|
||||
}
|
||||
|
||||
async fn highlight_css() -> HttpResponse {
|
||||
static CSS: Lazy<Bytes> = Lazy::new(|| {
|
||||
highlight::BAT_ASSETS.with(|s| {
|
||||
Bytes::from(css_for_theme_with_class_style(
|
||||
s.get_theme("OneHalfDark"),
|
||||
ClassStyle::Spaced,
|
||||
))
|
||||
})
|
||||
});
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/css")
|
||||
.body(CSS.clone())
|
||||
}
|
||||
|
||||
fn render_template<T: Template>(req: &HttpRequest, template: &T) -> Result<HttpResponse, Error> {
|
||||
match template.render() {
|
||||
Ok(html) => Ok(HttpResponse::Ok().content_type("text/html").body(html)),
|
||||
Err(e) => {
|
||||
error!("Error while rendering template for {}: {}", req.uri(), e);
|
||||
Err(InternalServerError(Box::new(e)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let result = rocket::ignite()
|
||||
.mount("/", routes![index, submit, submit_raw, show_paste])
|
||||
.launch()
|
||||
.await;
|
||||
|
||||
if let Err(e) = result {
|
||||
eprintln!("Failed to launch Rocket: {:#?}", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use rocket::request::{FromRequest, Outcome};
|
||||
use rocket::Request;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use actix_web::{
|
||||
dev::Payload,
|
||||
http::header::{self, HeaderValue},
|
||||
FromRequest, HttpMessage, HttpRequest,
|
||||
};
|
||||
use futures::future::ok;
|
||||
|
||||
/// Holds a value that determines whether or not this request wanted a plaintext response.
|
||||
///
|
||||
|
@ -19,26 +21,22 @@ impl Deref for IsPlaintextRequest {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for IsPlaintextRequest {
|
||||
type Error = ();
|
||||
impl FromRequest for IsPlaintextRequest {
|
||||
type Error = actix_web::Error;
|
||||
type Future = futures::future::Ready<Result<Self, Self::Error>>;
|
||||
|
||||
async fn from_request(request: &'a Request<'r>) -> Outcome<IsPlaintextRequest, ()> {
|
||||
if let Some(format) = request.format() {
|
||||
if format.is_plain() {
|
||||
return Outcome::Success(IsPlaintextRequest(true));
|
||||
}
|
||||
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||
if req.content_type() == "text/plain" {
|
||||
return ok(IsPlaintextRequest(true));
|
||||
}
|
||||
|
||||
match request
|
||||
match req
|
||||
.headers()
|
||||
.get_one("User-Agent")
|
||||
.and_then(|u| u.splitn(2, '/').next())
|
||||
.get(header::USER_AGENT)
|
||||
.and_then(|u| u.to_str().unwrap().split('/').next())
|
||||
{
|
||||
None | Some("Wget") | Some("curl") | Some("HTTPie") => {
|
||||
Outcome::Success(IsPlaintextRequest(true))
|
||||
}
|
||||
_ => Outcome::Success(IsPlaintextRequest(false)),
|
||||
None | Some("Wget" | "curl" | "HTTPie") => ok(IsPlaintextRequest(true)),
|
||||
_ => ok(IsPlaintextRequest(false)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,21 +45,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for IsPlaintextRequest {
|
|||
///
|
||||
/// The inner value of this `HostHeader` will be `None` if there was no Host header
|
||||
/// on the request.
|
||||
pub struct HostHeader<'a>(pub Option<&'a str>);
|
||||
pub struct HostHeader(pub Option<HeaderValue>);
|
||||
|
||||
impl<'a> Deref for HostHeader<'a> {
|
||||
type Target = Option<&'a str>;
|
||||
impl FromRequest for HostHeader {
|
||||
type Error = actix_web::Error;
|
||||
type Future = futures::future::Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn deref(&self) -> &Option<&'a str> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for HostHeader<'a> {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(request: &'a Request<'r>) -> Outcome<HostHeader<'a>, ()> {
|
||||
Outcome::Success(HostHeader(request.headers().get_one("Host")))
|
||||
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||
ok(Self(req.headers().get(header::HOST).cloned()))
|
||||
}
|
||||
}
|
||||
|
|
15
templates/404.html
Normal file
15
templates/404.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>404 Not Found</title>
|
||||
</head>
|
||||
<body align="center">
|
||||
<div align="center">
|
||||
<h1>404: Not Found</h1>
|
||||
<p>The requested resource could not be found.</p>
|
||||
<hr />
|
||||
<small>bin.</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
templates/500.html
Normal file
15
templates/500.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>500 Internal Server Error</title>
|
||||
</head>
|
||||
<body align="center">
|
||||
<div align="center">
|
||||
<h1>500: Internal Server Error</h1>
|
||||
<p>An error occurred while fetching the requested resource.</p>
|
||||
<hr />
|
||||
<small>bin.</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>bin.</title>
|
||||
|
||||
<link rel="help" href="https://github.com/w4/bin">
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body { margin: 0; }
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
padding: 2rem;
|
||||
|
@ -20,13 +16,11 @@
|
|||
color: #B0BEC5;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
line-height: 1.1;
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
{% block styles %}
|
||||
{% endblock styles %}
|
||||
{% block styles %}{% endblock styles %}
|
||||
</style>
|
||||
{% block head %}{% endblock head %}
|
||||
</head>
|
||||
<body>{% block content %}{% endblock content %}</body>
|
||||
</html>
|
||||
|
|
|
@ -9,12 +9,11 @@
|
|||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
counter-reset: line;
|
||||
counter-reset: line;
|
||||
}
|
||||
code {
|
||||
counter-increment: line;
|
||||
}
|
||||
|
||||
code::before {
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
|
@ -24,8 +23,10 @@
|
|||
color: #888;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
|
||||
{% endblock styles %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" type="text/css" href="highlight.css" />
|
||||
{% endblock head %}
|
||||
|
||||
{% block content %}<pre>{{ content|safe }}</pre>{% endblock content %}
|
Loading…
Add table
Add a link
Reference in a new issue