diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/consts.rs | 7 | ||||
-rw-r--r-- | src/listing.rs | 56 | ||||
-rw-r--r-- | src/main.rs | 37 | ||||
-rw-r--r-- | src/renderer.rs | 84 |
4 files changed, 77 insertions, 107 deletions
diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..d864683 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,7 @@ +use fast_qr::ECL; + +/// The error correction level to use for all QR code generation. +pub const QR_EC_LEVEL: ECL = ECL::L; + +/// The margin size for the SVG QR code on the webpage. +pub const SVG_QR_MARGIN: usize = 1; diff --git a/src/listing.rs b/src/listing.rs index 851f4ac..7477599 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -9,7 +9,6 @@ use actix_web::{HttpMessage, HttpRequest, HttpResponse}; use bytesize::ByteSize; use comrak::{markdown_to_html, ComrakOptions}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; -use qrcodegen::{QrCode, QrCodeEcc}; use serde::Deserialize; use strum_macros::{Display, EnumString}; @@ -38,7 +37,6 @@ pub struct QueryParameters { pub order: Option<SortingOrder>, pub raw: Option<bool>, pub mkdir_name: Option<String>, - qrcode: Option<String>, download: Option<ArchiveMethod>, } @@ -166,6 +164,12 @@ pub fn directory_listing( let base = Path::new(serve_path); let random_route_abs = format!("/{}", conf.route_prefix); + let abs_url = format!( + "{}://{}{}", + req.connection_info().scheme(), + req.connection_info().host(), + req.uri() + ); let is_root = base.parent().is_none() || Path::new(&req.path()) == Path::new(&random_route_abs); let encoded_dir = match base.strip_prefix(random_route_abs) { @@ -218,20 +222,6 @@ pub fn directory_listing( let query_params = extract_query_parameters(req); - // If the `qrcode` parameter is included in the url, then should respond to the QR code - if let Some(url) = query_params.qrcode { - let res = match QrCode::encode_text(&url, QrCodeEcc::Medium) { - Ok(qr) => HttpResponse::Ok() - .append_header(("Content-Type", "image/svg+xml")) - .body(qr_to_svg_string(&qr, 2)), - Err(err) => { - log::error!("URL is invalid (too long?): {:?}", err); - HttpResponse::UriTooLong().finish() - } - }; - return Ok(ServiceResponse::new(req.clone(), res)); - } - let mut entries: Vec<Entry> = Vec::new(); let mut readme: Option<(String, String)> = None; @@ -384,9 +374,10 @@ pub fn directory_listing( renderer::page( entries, readme, + abs_url, is_root, query_params, - breadcrumbs, + &breadcrumbs, &encoded_dir, conf, current_user, @@ -407,34 +398,3 @@ pub fn extract_query_parameters(req: &HttpRequest) -> QueryParameters { } } } - -// Returns a string of SVG code for an image depicting -// the given QR Code, with the given number of border modules. -// The string always uses Unix newlines (\n), regardless of the platform. -fn qr_to_svg_string(qr: &QrCode, border: i32) -> String { - assert!(border >= 0, "Border must be non-negative"); - let mut result = String::new(); - result += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; - result += "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"; - let dimension = qr - .size() - .checked_add(border.checked_mul(2).unwrap()) - .unwrap(); - result += &format!( - "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n", dimension); - result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n"; - result += "\t<path d=\""; - for y in 0..qr.size() { - for x in 0..qr.size() { - if qr.get_module(x, y) { - if x != 0 || y != 0 { - result += " "; - } - result += &format!("M{},{}h1v1h-1z", x + border, y + border); - } - } - } - result += "\" fill=\"#000000\"/>\n"; - result += "</svg>\n"; - result -} diff --git a/src/main.rs b/src/main.rs index 08c6680..b49089b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,14 +11,15 @@ use actix_web::{middleware, App, HttpRequest, HttpResponse}; use actix_web_httpauth::middleware::HttpAuthentication; use anyhow::Result; use clap::{crate_version, IntoApp, Parser}; +use fast_qr::QRBuilder; use log::{error, warn}; -use qrcodegen::{QrCode, QrCodeEcc}; use yansi::{Color, Paint}; mod archive; mod args; mod auth; mod config; +mod consts; mod errors; mod file_upload; mod listing; @@ -238,13 +239,13 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { .iter() .filter(|url| !url.contains("//127.0.0.1:") && !url.contains("//[::1]:")) { - match QrCode::encode_text(url, QrCodeEcc::Low) { + match QRBuilder::new(url.clone()).ecl(consts::QR_EC_LEVEL).build() { Ok(qr) => { println!("QR code for {}:", Color::Green.paint(url).bold()); - print_qr(&qr); + qr.print(); } Err(e) => { - error!("Failed to render QR to terminal: {}", e); + error!("Failed to render QR to terminal: {:?}", e); } }; } @@ -350,31 +351,3 @@ async fn css() -> impl Responder { .insert_header(ContentType(mime::TEXT_CSS)) .body(css) } - -// Prints to the console two inverted QrCodes side by side. -fn print_qr(qr: &QrCode) { - let border = 4; - let size = qr.size() + 2 * border; - - for y in (0..size).step_by(2) { - for x in 0..2 * size { - let inverted = x >= size; - let (x, y) = (x % size - border, y - border); - - //each char represents two vertical modules - let (mod1, mod2) = match inverted { - false => (qr.get_module(x, y), qr.get_module(x, y + 1)), - true => (!qr.get_module(x, y), !qr.get_module(x, y + 1)), - }; - let c = match (mod1, mod2) { - (false, false) => ' ', - (true, false) => '▀', - (false, true) => '▄', - (true, true) => '█', - }; - print!("{0}", c); - } - println!(); - } - println!(); -} diff --git a/src/renderer.rs b/src/renderer.rs index 40aa7cd..c8958fe 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -2,11 +2,15 @@ use actix_web::http::StatusCode; use chrono::{DateTime, Utc}; use chrono_humanize::Humanize; use clap::{crate_name, crate_version}; +use fast_qr::convert::svg::SvgBuilder; +use fast_qr::qr::QRCodeError; +use fast_qr::QRBuilder; use maud::{html, Markup, PreEscaped, DOCTYPE}; use std::time::SystemTime; use strum::IntoEnumIterator; use crate::auth::CurrentUser; +use crate::consts; use crate::listing::{Breadcrumb, Entry, QueryParameters, SortingMethod, SortingOrder}; use crate::{archive::ArchiveMethod, MiniserveConfig}; @@ -15,9 +19,10 @@ use crate::{archive::ArchiveMethod, MiniserveConfig}; pub fn page( entries: Vec<Entry>, readme: Option<(String, String)>, + abs_url: impl AsRef<str>, is_root: bool, query_params: QueryParameters, - breadcrumbs: Vec<Breadcrumb>, + breadcrumbs: &[Breadcrumb], encoded_dir: &str, conf: &MiniserveConfig, current_user: Option<&CurrentUser>, @@ -33,11 +38,7 @@ pub fn page( let upload_action = build_upload_action(&upload_route, encoded_dir, sort_method, sort_order); let mkdir_action = build_mkdir_action(&upload_route, encoded_dir); - let title_path = breadcrumbs - .iter() - .map(|el| el.name.clone()) - .collect::<Vec<_>>() - .join("/"); + let title_path = breadcrumbs_to_path_string(breadcrumbs); html! { (DOCTYPE) @@ -89,7 +90,10 @@ pub fn page( } } } - (color_scheme_selector(conf.show_qrcode, conf.hide_theme_selector)) + nav { + (qr_spoiler(conf.show_qrcode, abs_url)) + (color_scheme_selector(conf.hide_theme_selector)) + } div.container { span #top { } h1.title dir="ltr" { @@ -226,6 +230,25 @@ pub fn raw(entries: Vec<Entry>, is_root: bool) -> Markup { } } +/// Renders the QR code SVG +fn qr_code_svg(url: impl AsRef<str>, margin: usize) -> Result<String, QRCodeError> { + let qr = QRBuilder::new(url.as_ref().into()) + .ecl(consts::QR_EC_LEVEL) + .build()?; + let svg = SvgBuilder::new().margin(margin).build_qr(qr); + + Ok(svg) +} + +/// Build a path string from a list of breadcrumbs. +fn breadcrumbs_to_path_string(breadcrumbs: &[Breadcrumb]) -> String { + breadcrumbs + .iter() + .map(|el| el.name.clone()) + .collect::<Vec<_>>() + .join("/") +} + // Partial: version footer fn version_footer() -> Markup { html! { @@ -292,30 +315,37 @@ const THEME_PICKER_CHOICES: &[(&str, &str)] = &[ pub const THEME_SLUGS: &[&str] = &["squirrel", "archlinux", "zenburn", "monokai"]; -/// Partial: color scheme selector -fn color_scheme_selector(show_qrcode: bool, hide_theme_selector: bool) -> Markup { +/// Partial: qr code spoiler +fn qr_spoiler(show_qrcode: bool, content: impl AsRef<str>) -> Markup { html! { - nav { - @if show_qrcode { - div { - p onmouseover="document.querySelector('#qrcode').src = `?qrcode=${encodeURIComponent(window.location.href)}`" { - "QR code" - } - div.qrcode { - img #qrcode alt="QR code" title="QR code of this page"; + @if show_qrcode { + div { + p { + "QR code" + } + div.qrcode #qrcode title=(PreEscaped(content.as_ref())) { + @match qr_code_svg(content, consts::SVG_QR_MARGIN) { + Ok(svg) => (PreEscaped(svg)), + Err(err) => (format!("QR generation error: {:?}", err)), } } } - @if !hide_theme_selector { - div { - p { - "Change theme..." - } - ul.theme { - @for color_scheme in THEME_PICKER_CHOICES { - li.(format!("theme_{}", color_scheme.1)) { - (color_scheme_link(color_scheme)) - } + } + } +} + +/// Partial: color scheme selector +fn color_scheme_selector(hide_theme_selector: bool) -> Markup { + html! { + @if !hide_theme_selector { + div { + p { + "Change theme..." + } + ul.theme { + @for color_scheme in THEME_PICKER_CHOICES { + li.(format!("theme_{}", color_scheme.1)) { + (color_scheme_link(color_scheme)) } } } |