#![allow(unused_variables)] #![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] #[macro_use] extern crate askama; #[macro_use] extern crate lazy_static; extern crate actix; extern crate actix_web; extern crate crypto; extern crate bytes; extern crate env_logger; extern crate futures; extern crate rand; extern crate syntect; use actix_web::http::{Method, StatusCode}; use actix_web::{  middleware, pred, server, App, Path, HttpRequest, HttpMessage, HttpResponse, Result, FutureResponse, AsyncResponder }; use askama::Template; use crypto::hmac::Hmac; use crypto::mac::Mac; use crypto::sha2::Sha256; use bytes::Bytes; use futures::future::Future; use rand::Rng; use syntect::easy::HighlightLines; use syntect::highlighting::{Theme, ThemeSet, Style}; use syntect::html::highlighted_snippet_for_string; use syntect::parsing::SyntaxSet; use syntect::util::as_24_bit_terminal_escaped; use std::env; use std::fs::File; use std::io::Write; //use std::path::Path; const BASE62: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const UPLOADS_DIR: &'static str = "uploads"; const ID_LEN: usize = 5; const KEY_BYTES: usize = 8; const MAX_PASTE_BYTES: usize = 2 * 1024 * 1024; // 2 MB const PASTE_DAYS: u32 = 30; // u32 needed for Duration checked_mul() lazy_static! {  static ref HMAC_KEY: String = {  String::from_utf8(std::fs::read("hmac_key.txt").expect("Reading HMAC key")).expect("Corrupt HMAC key")  };  static ref HL_THEME: Theme = {  let ts = ThemeSet::load_defaults();  let theme = &ts.themes["base16-eighties.dark"];  theme.clone()  }; } // SyntaxSet does not implement Copy/Sync, so we do it like this. // see https://github.com/trishume/syntect/issues/20 thread_local! {  static SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); } #[derive(Debug)] enum HighlightedText {  Terminal(String),  Html(String),  Error(String) } #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate<'a> {  host: &'a str,  scheme: &'a str,  id: &'a str,  key: &'a str,  ext: &'a str, } #[derive(Template)] #[template(path = "help.html")] struct HelpTemplate<'a> {  host: &'a str,  scheme: &'a str,  id: &'a str,  key: &'a str,  ext: &'a str, } #[derive(Template)] #[template(path = "paste_html.html")] struct PasteTemplate<'a> {  paste: &'a str, } // syntax highlighter helper function fn highlight(buffer: String, lang: &str, html: bool) -> HighlightedText {  SYNTAX_SET.with(|ss| {  let syntax = ss.find_syntax_by_extension(lang).unwrap_or_else(|| ss.find_syntax_plain_text());  if syntax.name == "Plain Text" {  return HighlightedText::Error(format!("Requested highlight \"{}\" not available", lang));  }  if html {  HighlightedText::Html(highlighted_snippet_for_string(&buffer, syntax, &HL_THEME))  } else {  let mut highlighter = HighlightLines::new(syntax, &HL_THEME);  let mut output = String::new();  for line in buffer.lines() {  let ranges: Vec<(Style, &str)> = highlighter.highlight(line);  let escaped;  escaped = as_24_bit_terminal_escaped(&ranges[..], false);  output += &format!("{}\n", escaped);  }  HighlightedText::Terminal(output)  }  }) } fn generate_id(size: usize) -> String {  let mut id = String::with_capacity(size);  let mut rng = rand::thread_rng();  for _ in 0..size {  id.push(BASE62[rng.gen::<usize>() % 62] as char);  }  id } fn gen_key(input: &str) -> String {  let mut hmac = Hmac::new(Sha256::new(), HMAC_KEY.as_bytes());  hmac.input(input.as_bytes());  let hmac_result = hmac.result();  let key: String = hmac_result.code().iter()  .take(KEY_BYTES)  .map(|b| format!("{:02X}", b))  .collect();  key.to_lowercase() } /// usage template handler fn usage(req: HttpRequest) -> Result<HttpResponse> {  let s = IndexTemplate {  host: req.connection_info().host(),  scheme: req.connection_info().scheme(),  id: "vxcRz",  key: "a7772362cf6e2c36",  ext: "rs",  }.render().unwrap();  Ok(HttpResponse::Ok().content_type("text/plain; charset=utf-8").body(s)) } /// full usage template handler fn help(req: HttpRequest) -> Result<HttpResponse> {  // TODO: combine this with the usage handler if possible.  let s = HelpTemplate {  host: req.connection_info().host(),  scheme: req.connection_info().scheme(),  id: "vxcRz",  key: "a7772362cf6e2c36",  ext: "rs",  }.render().unwrap();  Ok(HttpResponse::Ok().content_type("text/plain; charset=utf-8").body(s)) } /// paste retrieve handler fn retrieve(req: HttpRequest) -> Result<HttpResponse> {  let path = format!("uploads/{}", req.match_info().get("id").unwrap_or(""));  match std::fs::read(path) {  Ok(buffer) => {  // able to open the file  match req.match_info().get("aux") {  Some(lang) => {  // syntax highlighting  let html_output = match req.headers().get("accept") {  Some(a) => a.to_str().unwrap_or("").contains("text/html"),  None => false  };  match highlight(String::from_utf8_lossy(&buffer).to_string(), lang, html_output) {  HighlightedText::Terminal(s) => Ok(HttpResponse::build(StatusCode::OK)  .content_type("text/plain; charset=utf-8")  .body(s)),  HighlightedText::Html(s) => {  let rendered = PasteTemplate {  paste: &s  }.render().unwrap();  Ok(HttpResponse::build(StatusCode::OK)  .content_type("text/html; charset=utf-8")  .body(rendered))  },  HighlightedText::Error(s) => Ok(HttpResponse::BadRequest().content_type("text/plain; charset=utf-8").body(format!("Invalid request: {}.\n", s)))  }  },  None => {  // no syntax highlighting  Ok(HttpResponse::build(StatusCode::OK)  .content_type("text/plain; charset=utf-8")  .body(buffer))  }  }  },  Err(_) => Ok(HttpResponse::NotFound().content_type("text/plain; charset=utf-8").body("Not Found\n"))  } } /// paste submission handler fn submit(req: HttpRequest) -> FutureResponse<HttpResponse> {  let base_url = format!("{scheme}://{host}", scheme = req.connection_info().scheme(), host = req.connection_info().host());  req.body()  .limit(MAX_PASTE_BYTES)  .from_err()  .and_then(move |bytes: Bytes| {  // determine paste URL  let mut id: String;  let mut path: String;  let mut double_id_len = ID_LEN * 2; // so we increase by 1 every two loops  loop {  id = generate_id(double_id_len / 2);  path = format!("uploads/{id}", id = id);  if !std::path::Path::new(&path).exists() {  break;  }  double_id_len += 1;  }  let url = format!("{base_url}/{id}", base_url = base_url, id = id);  // write the file  let mut f = File::create(path)?;  f.write_all(&bytes)?;  // return the response  Ok(HttpResponse::Ok().body(format!(  "View URL: {url}\nEdit URL: {url}/{key}\n\nThis paste will be deleted in {days} days.\n",  url = url, key = gen_key(&id), days = PASTE_DAYS)).into())  }).responder() } /// paste replace handler fn replace(req: HttpRequest) -> FutureResponse<HttpResponse> {  // TODO: it'd be nice if we could get the path info less suckily  let id = req.match_info().get("id").unwrap_or("").to_string();  let key = req.match_info().get("aux").unwrap_or("").to_string();  // replace it with request data  let base_url = format!("{scheme}://{host}", scheme = req.connection_info().scheme(), host = req.connection_info().host());  let path = format!("{}/{}", UPLOADS_DIR, id);  req.body()  .limit(MAX_PASTE_BYTES)  .from_err()  .and_then(move |bytes: Bytes| {  // verify key  if key != gen_key(&id) {  return Ok(HttpResponse::Unauthorized().content_type("text/plain; charset=utf-8").body("Unauthorized: Invalid key\n")).into();  }  let url = format!("{base_url}/{id}", base_url = base_url, id = id);  // write the file  let mut f = File::create(path)?;  f.write_all(&bytes)?;  // return the response  Ok(HttpResponse::Ok().body(format!(  "View URL: {url}\nEdit URL: {url}/{key}\n\nThis paste will be deleted in {days} days.\n",  url = url, key = gen_key(&id), days = PASTE_DAYS)).into())  }).responder() } /// paste deletion handler fn delete(info: Path<(String, String)>) -> Result<HttpResponse> {  let id = &info.0;  let key = &info.1;  if key != &gen_key(&id) {  return Ok(HttpResponse::Unauthorized().content_type("text/plain; charset=utf-8").body("Unauthorized: Invalid key\n"));  }  // delete file  std::fs::remove_file(format!("{}/{}", UPLOADS_DIR, id))?;  Ok(HttpResponse::Ok().content_type("text/plain; charset=utf-8").body("Paste deleted\n")) } fn main() {  //env::set_var("RUST_BACKTRACE", "1");  env::set_var("RUST_LOG", "actix_web=debug");  env_logger::init();  let sys = actix::System::new("pastebin-actix");  let addr = server::new(  || App::new()  .middleware(middleware::Logger::default())  .resource("/", |r| {  r.get().f(usage);  r.post().f(submit);  })  .resource("/help", |r| r.method(Method::GET).f(help))  //.resource("/webupload", |r| r.method(Method::GET).f(webupload))  .resource("/{id}", |r| r.method(Method::GET).f(retrieve))  .resource("/{id}/{aux}", |r| {  r.put().f(replace);  r.get().with(retrieve);  r.delete().with(delete);  })  .default_resource(|r| {  r.method(Method::GET).f(|req|  HttpResponse::NotFound().content_type("text/plain; charset=utf-8").body("Not Found\n"));  r.route().filter(pred::Not(pred::Get())).f(|req|  HttpResponse::BadRequest().content_type("text/plain; charset=utf-8").body("Bad Request\n"));  }))  .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")  .shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s)  .start();  println!("Starting http server: 127.0.0.1:8080");  let _ = sys.run(); }