aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSven-Hendrik Haase <svenstaro@gmail.com>2022-09-18 03:40:33 +0000
committerGitHub <noreply@github.com>2022-09-18 03:40:33 +0000
commit9b4be68d7ecbf7737ff62c03017b5111f12a4a28 (patch)
treeef648b44467de162cb75e8498554f7954f9601f7 /src
parentMerge pull request #905 from svenstaro/dependabot/cargo/sha2-0.10.6 (diff)
parentMerge branch 'master' into qrcode (diff)
downloadminiserve-9b4be68d7ecbf7737ff62c03017b5111f12a4a28.tar.gz
miniserve-9b4be68d7ecbf7737ff62c03017b5111f12a4a28.zip
Merge pull request #848 from cyqsimon/qrcode
Switch to `fast_qr` lib
Diffstat (limited to 'src')
-rw-r--r--src/consts.rs7
-rw-r--r--src/listing.rs56
-rw-r--r--src/main.rs37
-rw-r--r--src/renderer.rs84
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))
}
}
}