From 8543d2d61fbafde6222e9b95f9604b41570db477 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Wed, 20 Jul 2022 16:26:08 +0800
Subject: [PATCH 01/19] Switch to `qrcode` lib
---
Cargo.lock | 68 +++++++++++++++++++++++++++++++++++++++++++++----
Cargo.toml | 2 +-
src/consts.rs | 4 +++
src/listing.rs | 41 ++++-------------------------
src/main.rs | 52 ++++++++++++++++++-------------------
src/renderer.rs | 40 +++++++++++++++++++++++++++++
6 files changed, 138 insertions(+), 69 deletions(-)
create mode 100644 src/consts.rs
diff --git a/Cargo.lock b/Cargo.lock
index 174d5bc58..62c1ff0f1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -474,6 +474,12 @@ version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
+[[package]]
+name = "bytemuck"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -522,6 +528,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "checked_int_cast"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
+
[[package]]
name = "chrono"
version = "0.4.22"
@@ -626,6 +638,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
[[package]]
name = "comrak"
version = "0.14.0"
@@ -1072,7 +1090,7 @@ dependencies = [
"indexmap",
"lasso",
"num-bigint",
- "num-rational",
+ "num-rational 0.4.1",
"num-traits",
"once_cell",
"phf 0.9.0",
@@ -1267,6 +1285,20 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "image"
+version = "0.23.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "num-iter",
+ "num-rational 0.3.2",
+ "num-traits",
+]
+
[[package]]
name = "indexmap"
version = "1.9.1"
@@ -1546,7 +1578,7 @@ dependencies = [
"port_check",
"predicates",
"pretty_assertions",
- "qrcodegen",
+ "qrcode",
"regex",
"reqwest",
"rstest",
@@ -1629,6 +1661,28 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "num-iter"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-rational"
version = "0.4.1"
@@ -2024,10 +2078,14 @@ dependencies = [
]
[[package]]
-name = "qrcodegen"
-version = "1.8.0"
+name = "qrcode"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142"
+checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f"
+dependencies = [
+ "checked_int_cast",
+ "image",
+]
[[package]]
name = "quote"
diff --git a/Cargo.toml b/Cargo.toml
index de73987d4..909488381 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,7 +44,7 @@ mime = "0.3"
nanoid = "0.4"
percent-encoding = "2"
port_check = "0.1"
-qrcodegen = "1"
+qrcode = "0.12.0"
rustls = { version = "0.20", optional = true }
rustls-pemfile = { version = "1.0", optional = true }
serde = { version = "1", features = ["derive"] }
diff --git a/src/consts.rs b/src/consts.rs
new file mode 100644
index 000000000..07f47b0da
--- /dev/null
+++ b/src/consts.rs
@@ -0,0 +1,4 @@
+use qrcode::EcLevel;
+
+/// The error correction level to use for all QR code generation.
+pub const QR_EC_LEVEL: EcLevel = EcLevel::M;
diff --git a/src/listing.rs b/src/listing.rs
index 82b4cdba0..f90c40c31 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -9,14 +9,14 @@ 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 qrcode::QrCode;
use serde::Deserialize;
use strum_macros::{Display, EnumString};
use crate::archive::ArchiveMethod;
use crate::auth::CurrentUser;
use crate::errors::{self, ContextualError};
-use crate::renderer;
+use crate::{consts, renderer};
use self::percent_encode_sets::PATH_SEGMENT;
@@ -220,10 +220,10 @@ pub fn directory_listing(
// 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) {
+ let res = match QrCode::with_error_correction_level(url, consts::QR_EC_LEVEL) {
Ok(qr) => HttpResponse::Ok()
- .append_header(("Content-Type", "image/svg+xml"))
- .body(qr_to_svg_string(&qr, 2)),
+ .content_type("text/html; charset=utf-8")
+ .body(renderer::qr_code_page(&qr).into_string()),
Err(err) => {
log::error!("URL is invalid (too long?): {:?}", err);
HttpResponse::UriTooLong().finish()
@@ -407,34 +407,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 += "\n";
- result += "\n";
- let dimension = qr
- .size()
- .checked_add(border.checked_mul(2).unwrap())
- .unwrap();
- result += &format!(
- "\n";
- result
-}
diff --git a/src/main.rs b/src/main.rs
index b46262db3..558dabb30 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,13 +13,14 @@ use anyhow::Result;
use clap::{crate_version, IntoApp, Parser};
use clap_complete::generate;
use log::{error, warn};
-use qrcodegen::{QrCode, QrCodeEcc};
+use qrcode::QrCode;
use yansi::{Color, Paint};
mod archive;
mod args;
mod auth;
mod config;
+mod consts;
mod errors;
mod file_upload;
mod listing;
@@ -239,7 +240,7 @@ 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 QrCode::with_error_correction_level(url, consts::QR_EC_LEVEL) {
Ok(qr) => {
println!("QR code for {}:", Color::Green.paint(url).bold());
print_qr(&qr);
@@ -352,30 +353,27 @@ async fn css() -> impl Responder {
.body(css)
}
-// Prints to the console two inverted QrCodes side by side.
+// Prints to the console a normal and an inverted QrCode 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!();
+ use qrcode::render::unicode::Dense1x2;
+
+ let normal = qr
+ .render()
+ .quiet_zone(true)
+ .dark_color(Dense1x2::Dark)
+ .light_color(Dense1x2::Light)
+ .build();
+ let inverted = qr
+ .render()
+ .quiet_zone(true)
+ .dark_color(Dense1x2::Light)
+ .light_color(Dense1x2::Dark)
+ .build();
+ let codes = normal
+ .lines()
+ .zip(inverted.lines())
+ .map(|(l, r)| format!("{} {}", l, r))
+ .collect::>()
+ .join("\n");
+ println!("{}", codes);
}
diff --git a/src/renderer.rs b/src/renderer.rs
index 7ec48b0e1..48b74b6d4 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -3,6 +3,7 @@ use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
use clap::{crate_name, crate_version};
use maud::{html, Markup, PreEscaped, DOCTYPE};
+use qrcode::QrCode;
use std::time::SystemTime;
use strum::IntoEnumIterator;
@@ -224,6 +225,45 @@ pub fn raw(entries: Vec, is_root: bool) -> Markup {
}
}
+/// Renders the QR code page
+pub fn qr_code_page(qr: &QrCode) -> Markup {
+ use qrcode::render::svg;
+
+ html! {
+ (DOCTYPE)
+ html {
+ body {
+ // make QR code expand and fill page
+ style {
+ (PreEscaped("\
+ html {\
+ width: 100vw;\
+ height: 100vh;\
+ }\
+ body {\
+ width: 100%;\
+ height: 100%;\
+ margin: 0;\
+ display: grid;\
+ align-items: center;\
+ justify-items: center;\
+ }\
+ svg {\
+ width: 80%;\
+ height: 80%;\
+ }\
+ "))
+ }
+ (PreEscaped(qr.render()
+ .quiet_zone(false)
+ .dark_color(svg::Color("#000000"))
+ .light_color(svg::Color("#ffffff"))
+ .build()))
+ }
+ }
+ }
+}
+
// Partial: version footer
fn version_footer() -> Markup {
html! {
From c2ece55040d4f1f03e189636895645435d762322 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Wed, 20 Jul 2022 20:09:55 +0800
Subject: [PATCH 02/19] Fix tests
---
Cargo.lock | 7 ++++
Cargo.toml | 1 +
src/renderer.rs | 2 +-
tests/qrcode.rs | 85 +++++++++++++++++++++++++++++++++++++++----------
4 files changed, 77 insertions(+), 18 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 62c1ff0f1..e84e76b26 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -817,6 +817,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
+[[package]]
+name = "fake-tty"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1caac348c61d1f9a7d43c3629abc75540754a4971944953f84b98cb8280e3971"
+
[[package]]
name = "fancy-regex"
version = "0.7.1"
@@ -1563,6 +1569,7 @@ dependencies = [
"clap_complete",
"clap_mangen",
"comrak",
+ "fake-tty",
"futures",
"get_if_addrs",
"grass",
diff --git a/Cargo.toml b/Cargo.toml
index 909488381..1bac97ea2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -69,6 +69,7 @@ tls = ["rustls", "rustls-pemfile", "actix-web/rustls"]
[dev-dependencies]
assert_cmd = "2"
assert_fs = "1"
+fake-tty = "0.2.0"
predicates = "2"
pretty_assertions = "1.2"
regex = "1"
diff --git a/src/renderer.rs b/src/renderer.rs
index 48b74b6d4..95aeb5fb3 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -232,7 +232,7 @@ pub fn qr_code_page(qr: &QrCode) -> Markup {
html! {
(DOCTYPE)
html {
- body {
+ body.qr_code_page {
// make QR code expand and fill page
style {
(PreEscaped("\
diff --git a/tests/qrcode.rs b/tests/qrcode.rs
index a9c27fe85..eb37abc07 100644
--- a/tests/qrcode.rs
+++ b/tests/qrcode.rs
@@ -1,43 +1,94 @@
mod fixtures;
-use fixtures::{server, server_no_stderr, Error, TestServer};
+use assert_cmd::prelude::CommandCargoExt;
+use assert_fs::TempDir;
+use fixtures::{port, server_no_stderr, tmpdir, Error, TestServer};
use reqwest::StatusCode;
use rstest::rstest;
-use select::document::Document;
-use select::predicate::Attr;
-use std::iter::repeat_with;
+use std::process::Command;
+use std::thread::sleep;
+use std::time::Duration;
+
+fn run_in_faketty_kill_and_get_stdout(template: &Command) -> Result {
+ use fake_tty::{bash_command, get_stdout};
+
+ let cmd = {
+ let bin = template.get_program().to_str().expect("not UTF8");
+ let args = template
+ .get_args()
+ .map(|s| s.to_str().expect("not UTF8"))
+ .collect::>()
+ .join(" ");
+ format!("{} {}", bin, args)
+ };
+ let mut child = bash_command(&cmd).spawn()?;
+
+ sleep(Duration::from_secs(1));
+
+ child.kill()?;
+ let output = child.wait_with_output().expect("Failed to read stdout");
+ let all_text = get_stdout(output.stdout)?;
+
+ Ok(all_text)
+}
#[rstest]
-fn hide_qrcode_element(server: TestServer) -> Result<(), Error> {
- let body = reqwest::blocking::get(server.url())?.error_for_status()?;
- let parsed = Document::from_read(body)?;
- assert!(parsed.find(Attr("id", "qrcode")).next().is_none());
+fn qrcode_hidden_in_tty_when_disabled(tmpdir: TempDir, port: u16) -> Result<(), Error> {
+ let mut template = Command::cargo_bin("miniserve")?;
+ template.arg("-p").arg(port.to_string()).arg(tmpdir.path());
+ let output = run_in_faketty_kill_and_get_stdout(&template)?;
+
+ assert!(!output.contains("QR code for "));
Ok(())
}
#[rstest]
-fn show_qrcode_element(#[with(&["-q"])] server: TestServer) -> Result<(), Error> {
- let body = reqwest::blocking::get(server.url())?.error_for_status()?;
- let parsed = Document::from_read(body)?;
- assert!(parsed.find(Attr("id", "qrcode")).next().is_some());
+fn qrcode_shown_in_tty_when_enabled(tmpdir: TempDir, port: u16) -> Result<(), Error> {
+ let mut template = Command::cargo_bin("miniserve")?;
+ template
+ .arg("-p")
+ .arg(port.to_string())
+ .arg("-q")
+ .arg(tmpdir.path());
+
+ let output = run_in_faketty_kill_and_get_stdout(&template)?;
+
+ assert!(output.contains("QR code for "));
+ Ok(())
+}
+
+#[rstest]
+fn qrcode_hidden_in_non_tty_when_enabled(tmpdir: TempDir, port: u16) -> Result<(), Error> {
+ let mut child = Command::cargo_bin("miniserve")?
+ .arg("-p")
+ .arg(port.to_string())
+ .arg("-q")
+ .arg(tmpdir.path())
+ .spawn()?;
+
+ sleep(Duration::from_secs(1));
+
+ child.kill()?;
+ let output = child.wait_with_output().expect("Failed to read stdout");
+ let stdout = String::from_utf8(output.stdout)?;
+ assert!(!stdout.contains("QR code for "));
Ok(())
}
#[rstest]
fn get_svg_qrcode(#[from(server_no_stderr)] server: TestServer) -> Result<(), Error> {
// Ok
- let resp = reqwest::blocking::get(server.url().join("/?qrcode=test")?)?;
+ let resp = reqwest::blocking::get(server.url().join("?qrcode=test")?)?;
assert_eq!(resp.status(), StatusCode::OK);
- assert_eq!(resp.headers()["Content-Type"], "image/svg+xml");
let body = resp.text()?;
- assert!(body.starts_with("
Date: Thu, 21 Jul 2022 14:33:37 +0800
Subject: [PATCH 03/19] Use `mime` for content type
---
src/listing.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/listing.rs b/src/listing.rs
index f90c40c31..b201ebbe1 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -222,7 +222,7 @@ pub fn directory_listing(
if let Some(url) = query_params.qrcode {
let res = match QrCode::with_error_correction_level(url, consts::QR_EC_LEVEL) {
Ok(qr) => HttpResponse::Ok()
- .content_type("text/html; charset=utf-8")
+ .content_type(mime::TEXT_HTML_UTF_8)
.body(renderer::qr_code_page(&qr).into_string()),
Err(err) => {
log::error!("URL is invalid (too long?): {:?}", err);
From 3c0d0cfffe6a647ff91bd74ebe0c3085032fc646 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Sat, 23 Jul 2022 00:46:55 +0800
Subject: [PATCH 04/19] Disable broken tests for Windows
---
Cargo.toml | 5 ++++-
tests/qrcode.rs | 5 +++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index 1bac97ea2..1876320fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -69,7 +69,6 @@ tls = ["rustls", "rustls-pemfile", "actix-web/rustls"]
[dev-dependencies]
assert_cmd = "2"
assert_fs = "1"
-fake-tty = "0.2.0"
predicates = "2"
pretty_assertions = "1.2"
regex = "1"
@@ -78,5 +77,9 @@ rstest = "0.15"
select = "0.5"
url = "2"
+[target.'cfg(not(windows))'.dev-dependencies]
+# fake_tty does not support Windows for now
+fake-tty = "0.2.0"
+
[build-dependencies]
grass = "0.11"
diff --git a/tests/qrcode.rs b/tests/qrcode.rs
index eb37abc07..21dae6a12 100644
--- a/tests/qrcode.rs
+++ b/tests/qrcode.rs
@@ -9,6 +9,7 @@ use std::process::Command;
use std::thread::sleep;
use std::time::Duration;
+#[cfg(not(windows))]
fn run_in_faketty_kill_and_get_stdout(template: &Command) -> Result {
use fake_tty::{bash_command, get_stdout};
@@ -33,6 +34,8 @@ fn run_in_faketty_kill_and_get_stdout(template: &Command) -> Result Result<(), Error> {
let mut template = Command::cargo_bin("miniserve")?;
template.arg("-p").arg(port.to_string()).arg(tmpdir.path());
@@ -44,6 +47,8 @@ fn qrcode_hidden_in_tty_when_disabled(tmpdir: TempDir, port: u16) -> Result<(),
}
#[rstest]
+// Disabled for Windows because `fake_tty` does not currently support it.
+#[cfg(not(windows))]
fn qrcode_shown_in_tty_when_enabled(tmpdir: TempDir, port: u16) -> Result<(), Error> {
let mut template = Command::cargo_bin("miniserve")?;
template
From a99d8bdcadf4f822bb86a2710e5d73783ac7097a Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Tue, 26 Jul 2022 12:27:14 +0800
Subject: [PATCH 05/19] Move QR code page style to `style.scss`
---
data/style.scss | 22 ++++++++++++++++++++++
src/listing.rs | 23 ++++++++---------------
src/renderer.rs | 47 +++++++++++++++++------------------------------
3 files changed, 47 insertions(+), 45 deletions(-)
diff --git a/data/style.scss b/data/style.scss
index 0fcc90d77..a5b3707a5 100644
--- a/data/style.scss
+++ b/data/style.scss
@@ -17,7 +17,29 @@ $themes: squirrel, archlinux, monokai, zenburn;
@return $s;
}
+// make QR code expand and fill page
+// --------------------------------------------------
+html.qr_code_page {
+ width: 100vw;
+ height: 100vh;
+}
+
+html.qr_code_page body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ display: grid;
+ align-items: center;
+ justify-items: center;
+}
+
+html.qr_code_page svg {
+ width: 80%;
+ height: 80%;
+}
+
+// --------------------------------------------------
html {
font-smoothing: antialiased;
diff --git a/src/listing.rs b/src/listing.rs
index b201ebbe1..66e541c0b 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -154,10 +154,7 @@ pub async fn file_handler(req: HttpRequest) -> actix_web::Result io::Result {
+pub fn directory_listing(dir: &actix_files::Directory, req: &HttpRequest) -> io::Result {
let extensions = req.extensions();
let current_user: Option<&CurrentUser> = extensions.get::();
@@ -198,8 +195,7 @@ pub fn directory_listing(
}
Component::Normal(s) => {
name = s.to_string_lossy().to_string();
- link_accumulator
- .push_str(&(utf8_percent_encode(&name, PATH_SEGMENT).to_string() + "/"));
+ link_accumulator.push_str(&(utf8_percent_encode(&name, PATH_SEGMENT).to_string() + "/"));
}
_ => name = "".to_string(),
};
@@ -223,7 +219,7 @@ pub fn directory_listing(
let res = match QrCode::with_error_correction_level(url, consts::QR_EC_LEVEL) {
Ok(qr) => HttpResponse::Ok()
.content_type(mime::TEXT_HTML_UTF_8)
- .body(renderer::qr_code_page(&qr).into_string()),
+ .body(renderer::qr_code_page(&breadcrumbs, &qr, conf).into_string()),
Err(err) => {
log::error!("URL is invalid (too long?): {:?}", err);
HttpResponse::UriTooLong().finish()
@@ -301,9 +297,9 @@ pub fn directory_listing(
}
match query_params.sort.unwrap_or(SortingMethod::Name) {
- SortingMethod::Name => entries.sort_by(|e1, e2| {
- alphanumeric_sort::compare_str(e1.name.to_lowercase(), e2.name.to_lowercase())
- }),
+ SortingMethod::Name => {
+ entries.sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.to_lowercase(), e2.name.to_lowercase()))
+ }
SortingMethod::Size => entries.sort_by(|e1, e2| {
// If we can't get the size of the entry (directory for instance)
// let's consider it's 0b
@@ -371,10 +367,7 @@ pub fn directory_listing(
.content_type(archive_method.content_type())
.append_header(archive_method.content_encoding())
.append_header(("Content-Transfer-Encoding", "binary"))
- .append_header((
- "Content-Disposition",
- format!("attachment; filename={:?}", file_name),
- ))
+ .append_header(("Content-Disposition", format!("attachment; filename={:?}", file_name)))
.body(actix_web::body::BodyStream::new(rx)),
))
} else {
@@ -386,7 +379,7 @@ pub fn directory_listing(
readme,
is_root,
query_params,
- breadcrumbs,
+ &breadcrumbs,
&encoded_dir,
conf,
current_user,
diff --git a/src/renderer.rs b/src/renderer.rs
index 95aeb5fb3..0154ce092 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -18,7 +18,7 @@ pub fn page(
readme: Option<(String, String)>,
is_root: bool,
query_params: QueryParameters,
- breadcrumbs: Vec,
+ breadcrumbs: &[Breadcrumb],
encoded_dir: &str,
conf: &MiniserveConfig,
current_user: Option<&CurrentUser>,
@@ -34,11 +34,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::>()
- .join("/");
+ let title_path = breadcrumbs_to_path_string(breadcrumbs);
html! {
(DOCTYPE)
@@ -226,34 +222,16 @@ pub fn raw(entries: Vec, is_root: bool) -> Markup {
}
/// Renders the QR code page
-pub fn qr_code_page(qr: &QrCode) -> Markup {
+pub fn qr_code_page(breadcrumbs: &[Breadcrumb], qr: &QrCode, conf: &MiniserveConfig) -> Markup {
use qrcode::render::svg;
+ let title = breadcrumbs_to_path_string(breadcrumbs);
+
html! {
(DOCTYPE)
- html {
- body.qr_code_page {
- // make QR code expand and fill page
- style {
- (PreEscaped("\
- html {\
- width: 100vw;\
- height: 100vh;\
- }\
- body {\
- width: 100%;\
- height: 100%;\
- margin: 0;\
- display: grid;\
- align-items: center;\
- justify-items: center;\
- }\
- svg {\
- width: 80%;\
- height: 80%;\
- }\
- "))
- }
+ html.qr_code_page {
+ (page_header(&title, false, &conf.favicon_route, &conf.css_route))
+ body {
(PreEscaped(qr.render()
.quiet_zone(false)
.dark_color(svg::Color("#000000"))
@@ -264,6 +242,15 @@ pub fn qr_code_page(qr: &QrCode) -> Markup {
}
}
+/// 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::>()
+ .join("/")
+}
+
// Partial: version footer
fn version_footer() -> Markup {
html! {
From ef3ec4039e220d3dc4a83ac267101c4dc45dace8 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Tue, 26 Jul 2022 13:10:21 +0800
Subject: [PATCH 06/19] Use low EC level for QR code
---
src/consts.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/consts.rs b/src/consts.rs
index 07f47b0da..f10570a51 100644
--- a/src/consts.rs
+++ b/src/consts.rs
@@ -1,4 +1,4 @@
use qrcode::EcLevel;
/// The error correction level to use for all QR code generation.
-pub const QR_EC_LEVEL: EcLevel = EcLevel::M;
+pub const QR_EC_LEVEL: EcLevel = EcLevel::L;
From 8c5a5b6bce1c846279aa8fb215d6b412ca134338 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Tue, 26 Jul 2022 15:20:10 +0800
Subject: [PATCH 07/19] Fix QR test screwing up terminal
---
tests/qrcode.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/qrcode.rs b/tests/qrcode.rs
index 21dae6a12..32d943868 100644
--- a/tests/qrcode.rs
+++ b/tests/qrcode.rs
@@ -11,6 +11,8 @@ use std::time::Duration;
#[cfg(not(windows))]
fn run_in_faketty_kill_and_get_stdout(template: &Command) -> Result {
+ use std::process::Stdio;
+
use fake_tty::{bash_command, get_stdout};
let cmd = {
@@ -22,7 +24,7 @@ fn run_in_faketty_kill_and_get_stdout(template: &Command) -> Result
Date: Mon, 22 Aug 2022 17:17:33 +0800
Subject: [PATCH 08/19] Remove separators in CSS
---
data/style.scss | 5 -----
1 file changed, 5 deletions(-)
diff --git a/data/style.scss b/data/style.scss
index a5b3707a5..187b445d8 100644
--- a/data/style.scss
+++ b/data/style.scss
@@ -17,9 +17,6 @@ $themes: squirrel, archlinux, monokai, zenburn;
@return $s;
}
-// make QR code expand and fill page
-// --------------------------------------------------
-
html.qr_code_page {
width: 100vw;
height: 100vh;
@@ -39,8 +36,6 @@ html.qr_code_page svg {
height: 80%;
}
-// --------------------------------------------------
-
html {
font-smoothing: antialiased;
text-rendering: optimizeLegibility;
From 5e32400e20da831cee16272f0d1d52be6b7928ea Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Mon, 22 Aug 2022 17:33:06 +0800
Subject: [PATCH 09/19] Use SASS nesting
---
data/style.scss | 25 +++++++++++++------------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/data/style.scss b/data/style.scss
index 187b445d8..e3dc17e20 100644
--- a/data/style.scss
+++ b/data/style.scss
@@ -20,22 +20,23 @@ $themes: squirrel, archlinux, monokai, zenburn;
html.qr_code_page {
width: 100vw;
height: 100vh;
-}
-html.qr_code_page body {
- width: 100%;
- height: 100%;
- margin: 0;
- display: grid;
- align-items: center;
- justify-items: center;
-}
+ body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ display: grid;
+ align-items: center;
+ justify-items: center;
+ }
-html.qr_code_page svg {
- width: 80%;
- height: 80%;
+ svg {
+ width: 80%;
+ height: 80%;
+ }
}
+
html {
font-smoothing: antialiased;
text-rendering: optimizeLegibility;
From 682a84d960f2fc915d176ef1b64196c9ba6fc776 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Mon, 22 Aug 2022 18:21:23 +0800
Subject: [PATCH 10/19] Fix QR code test
---
tests/qrcode.rs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/qrcode.rs b/tests/qrcode.rs
index 32d943868..c2bebd685 100644
--- a/tests/qrcode.rs
+++ b/tests/qrcode.rs
@@ -5,14 +5,12 @@ use assert_fs::TempDir;
use fixtures::{port, server_no_stderr, tmpdir, Error, TestServer};
use reqwest::StatusCode;
use rstest::rstest;
-use std::process::Command;
+use std::process::{Command, Stdio};
use std::thread::sleep;
use std::time::Duration;
#[cfg(not(windows))]
fn run_in_faketty_kill_and_get_stdout(template: &Command) -> Result {
- use std::process::Stdio;
-
use fake_tty::{bash_command, get_stdout};
let cmd = {
@@ -72,6 +70,8 @@ fn qrcode_hidden_in_non_tty_when_enabled(tmpdir: TempDir, port: u16) -> Result<(
.arg(port.to_string())
.arg("-q")
.arg(tmpdir.path())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
.spawn()?;
sleep(Duration::from_secs(1));
From 4fca60cc5b06fff1aa7f9e52b774d868dc7bfe1a Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Mon, 22 Aug 2022 19:07:24 +0800
Subject: [PATCH 11/19] Fix rustfmt complaints
---
src/listing.rs | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/src/listing.rs b/src/listing.rs
index 66e541c0b..bc8dcfb8e 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -154,7 +154,10 @@ pub async fn file_handler(req: HttpRequest) -> actix_web::Result io::Result {
+pub fn directory_listing(
+ dir: &actix_files::Directory,
+ req: &HttpRequest,
+) -> io::Result {
let extensions = req.extensions();
let current_user: Option<&CurrentUser> = extensions.get::();
@@ -195,7 +198,8 @@ pub fn directory_listing(dir: &actix_files::Directory, req: &HttpRequest) -> io:
}
Component::Normal(s) => {
name = s.to_string_lossy().to_string();
- link_accumulator.push_str(&(utf8_percent_encode(&name, PATH_SEGMENT).to_string() + "/"));
+ link_accumulator
+ .push_str(&(utf8_percent_encode(&name, PATH_SEGMENT).to_string() + "/"));
}
_ => name = "".to_string(),
};
@@ -297,9 +301,9 @@ pub fn directory_listing(dir: &actix_files::Directory, req: &HttpRequest) -> io:
}
match query_params.sort.unwrap_or(SortingMethod::Name) {
- SortingMethod::Name => {
- entries.sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.to_lowercase(), e2.name.to_lowercase()))
- }
+ SortingMethod::Name => entries.sort_by(|e1, e2| {
+ alphanumeric_sort::compare_str(e1.name.to_lowercase(), e2.name.to_lowercase())
+ }),
SortingMethod::Size => entries.sort_by(|e1, e2| {
// If we can't get the size of the entry (directory for instance)
// let's consider it's 0b
@@ -367,7 +371,10 @@ pub fn directory_listing(dir: &actix_files::Directory, req: &HttpRequest) -> io:
.content_type(archive_method.content_type())
.append_header(archive_method.content_encoding())
.append_header(("Content-Transfer-Encoding", "binary"))
- .append_header(("Content-Disposition", format!("attachment; filename={:?}", file_name)))
+ .append_header((
+ "Content-Disposition",
+ format!("attachment; filename={:?}", file_name),
+ ))
.body(actix_web::body::BodyStream::new(rx)),
))
} else {
From 9f7d5a8aeaf252fb51d88b965b3a324e8e815f37 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Thu, 1 Sep 2022 19:37:39 +0800
Subject: [PATCH 12/19] Fix drop-down QR code
---
Cargo.lock | 1 +
Cargo.toml | 3 +-
data/style.scss | 25 +++----------
src/listing.rs | 26 +++++---------
src/renderer.rs | 95 +++++++++++++++++++++++++++++--------------------
5 files changed, 73 insertions(+), 77 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index e84e76b26..cc8d3f623 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1576,6 +1576,7 @@ dependencies = [
"hex",
"http",
"httparse",
+ "lazy_static",
"libflate",
"log",
"maud",
diff --git a/Cargo.toml b/Cargo.toml
index 1876320fb..b7eb5208f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,6 +37,7 @@ get_if_addrs = "0.5"
hex = "0.4"
http = "0.2"
httparse = "1"
+lazy_static = "1.4.0"
libflate = "1"
log = "0.4"
maud = "0.23"
@@ -45,6 +46,7 @@ nanoid = "0.4"
percent-encoding = "2"
port_check = "0.1"
qrcode = "0.12.0"
+regex = "1"
rustls = { version = "0.20", optional = true }
rustls-pemfile = { version = "1.0", optional = true }
serde = { version = "1", features = ["derive"] }
@@ -71,7 +73,6 @@ assert_cmd = "2"
assert_fs = "1"
predicates = "2"
pretty_assertions = "1.2"
-regex = "1"
reqwest = { version = "0.11", features = ["blocking", "multipart", "rustls-tls"], default-features = false }
rstest = "0.15"
select = "0.5"
diff --git a/data/style.scss b/data/style.scss
index e3dc17e20..87ecc4c58 100644
--- a/data/style.scss
+++ b/data/style.scss
@@ -17,26 +17,6 @@ $themes: squirrel, archlinux, monokai, zenburn;
@return $s;
}
-html.qr_code_page {
- width: 100vw;
- height: 100vh;
-
- body {
- width: 100%;
- height: 100%;
- margin: 0;
- display: grid;
- align-items: center;
- justify-items: center;
- }
-
- svg {
- width: 80%;
- height: 80%;
- }
-}
-
-
html {
font-smoothing: antialiased;
text-rendering: optimizeLegibility;
@@ -198,8 +178,11 @@ nav .qrcode {
background: var(--switch_theme_background);
}
-nav .qrcode img {
+nav .qrcode svg {
display: block;
+ border: 0.3rem;
+ border-style: solid;
+ border-color: #ffffff;
}
nav .theme {
diff --git a/src/listing.rs b/src/listing.rs
index bc8dcfb8e..b00064974 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -9,14 +9,13 @@ 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 qrcode::QrCode;
use serde::Deserialize;
use strum_macros::{Display, EnumString};
use crate::archive::ArchiveMethod;
use crate::auth::CurrentUser;
use crate::errors::{self, ContextualError};
-use crate::{consts, renderer};
+use crate::renderer;
use self::percent_encode_sets::PATH_SEGMENT;
@@ -38,7 +37,6 @@ pub struct QueryParameters {
pub order: Option,
pub raw: Option,
pub mkdir_name: Option,
- qrcode: Option,
download: Option,
}
@@ -166,6 +164,13 @@ 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()
+ );
+ dbg!(&abs_url);
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 +223,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::with_error_correction_level(url, consts::QR_EC_LEVEL) {
- Ok(qr) => HttpResponse::Ok()
- .content_type(mime::TEXT_HTML_UTF_8)
- .body(renderer::qr_code_page(&breadcrumbs, &qr, conf).into_string()),
- Err(err) => {
- log::error!("URL is invalid (too long?): {:?}", err);
- HttpResponse::UriTooLong().finish()
- }
- };
- return Ok(ServiceResponse::new(req.clone(), res));
- }
-
let mut entries: Vec = Vec::new();
let mut readme: Option<(String, String)> = None;
@@ -384,6 +375,7 @@ pub fn directory_listing(
renderer::page(
entries,
readme,
+ abs_url,
is_root,
query_params,
&breadcrumbs,
diff --git a/src/renderer.rs b/src/renderer.rs
index 0154ce092..614672f90 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -2,12 +2,15 @@ use actix_web::http::StatusCode;
use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
use clap::{crate_name, crate_version};
+use lazy_static::lazy_static;
use maud::{html, Markup, PreEscaped, DOCTYPE};
-use qrcode::QrCode;
+use qrcode::{types::QrError, QrCode};
+use regex::Regex;
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};
@@ -16,6 +19,7 @@ use crate::{archive::ArchiveMethod, MiniserveConfig};
pub fn page(
entries: Vec,
readme: Option<(String, String)>,
+ abs_url: impl AsRef,
is_root: bool,
query_params: QueryParameters,
breadcrumbs: &[Breadcrumb],
@@ -86,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" {
@@ -221,25 +228,30 @@ pub fn raw(entries: Vec, is_root: bool) -> Markup {
}
}
-/// Renders the QR code page
-pub fn qr_code_page(breadcrumbs: &[Breadcrumb], qr: &QrCode, conf: &MiniserveConfig) -> Markup {
+/// Renders the QR code SVG
+fn qr_code_svg(url: impl AsRef, no_width_height_attr: bool) -> Result {
use qrcode::render::svg;
-
- let title = breadcrumbs_to_path_string(breadcrumbs);
-
- html! {
- (DOCTYPE)
- html.qr_code_page {
- (page_header(&title, false, &conf.favicon_route, &conf.css_route))
- body {
- (PreEscaped(qr.render()
- .quiet_zone(false)
- .dark_color(svg::Color("#000000"))
- .light_color(svg::Color("#ffffff"))
- .build()))
- }
+ let qr = QrCode::with_error_correction_level(url.as_ref(), consts::QR_EC_LEVEL)?;
+ let mut svg = qr
+ .render()
+ .quiet_zone(false)
+ .dark_color(svg::Color("#000000"))
+ .light_color(svg::Color("#ffffff"))
+ .build();
+
+ if no_width_height_attr {
+ // HACK: qrcode crate hard-codes height and width into SVG's attributes.
+ // This behaviour may be undesirable because we want it to fit its HTML container.
+ // The proper way to remove them is to use a XML parser, but regex is good enough for a
+ // simple case like this.
+ lazy_static! {
+ static ref RE: Regex =
+ Regex::new(r#"(?P.+?>)"#).unwrap();
}
+ svg = RE.replace(&svg, "$front$aft").to_string();
}
+
+ Ok(svg)
}
/// Build a path string from a list of breadcrumbs.
@@ -317,30 +329,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) -> 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 {
+ @match qr_code_svg(content, true) {
+ 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))
}
}
}
From 97db5065f4c02835509560b920cbd9680eac3f78 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Thu, 1 Sep 2022 19:41:49 +0800
Subject: [PATCH 13/19] Remove outdated test
---
tests/qrcode.rs | 22 +---------------------
1 file changed, 1 insertion(+), 21 deletions(-)
diff --git a/tests/qrcode.rs b/tests/qrcode.rs
index c2bebd685..162c33af6 100644
--- a/tests/qrcode.rs
+++ b/tests/qrcode.rs
@@ -2,8 +2,7 @@ mod fixtures;
use assert_cmd::prelude::CommandCargoExt;
use assert_fs::TempDir;
-use fixtures::{port, server_no_stderr, tmpdir, Error, TestServer};
-use reqwest::StatusCode;
+use fixtures::{port, tmpdir, Error};
use rstest::rstest;
use std::process::{Command, Stdio};
use std::thread::sleep;
@@ -83,22 +82,3 @@ fn qrcode_hidden_in_non_tty_when_enabled(tmpdir: TempDir, port: u16) -> Result<(
assert!(!stdout.contains("QR code for "));
Ok(())
}
-
-#[rstest]
-fn get_svg_qrcode(#[from(server_no_stderr)] server: TestServer) -> Result<(), Error> {
- // Ok
- let resp = reqwest::blocking::get(server.url().join("?qrcode=test")?)?;
-
- assert_eq!(resp.status(), StatusCode::OK);
- let body = resp.text()?;
- assert!(body.contains("qr_code_page"));
- assert!(body.contains("