diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | src/listing.rs | 26 | ||||
-rw-r--r-- | tests/fixtures/mod.rs | 3 | ||||
-rw-r--r-- | tests/serve_request.rs | 6 |
4 files changed, 27 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 04358eb..25b0a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate - Fix breadcrumbs for right-to-left languages [#489](https://github.com/svenstaro/miniserve/pull/489) (thanks @aliemjay) +- Fix URL percent encoding for special characters [#485](https://github.com/svenstaro/miniserve/pull/485) (thanks @aliemjay) ## [0.13.0] - 2021-03-28 - Change default log level to `Warn` diff --git a/src/listing.rs b/src/listing.rs index 66aea6b..8c01b4b 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -4,7 +4,7 @@ use actix_web::http::StatusCode; use actix_web::web::Query; use actix_web::{HttpRequest, HttpResponse, Result}; use bytesize::ByteSize; -use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, CONTROLS}; +use percent_encoding::{percent_decode_str, utf8_percent_encode}; use qrcodegen::{QrCode, QrCodeEcc}; use serde::Deserialize; use std::io; @@ -15,8 +15,17 @@ use strum_macros::{Display, EnumString}; use crate::archive::CompressionMethod; use crate::errors::{self, ContextualError}; use crate::renderer; - -const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); +use percent_encode_sets::PATH_SEGMENT; + +/// "percent-encode sets" as defined by WHATWG specs: +/// https://url.spec.whatwg.org/#percent-encoded-bytes +mod percent_encode_sets { + use percent_encoding::{AsciiSet, CONTROLS}; + const BASE: &AsciiSet = &CONTROLS.add(b'%'); + pub const QUERY: &AsciiSet = &BASE.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); + pub const PATH: &AsciiSet = &QUERY.add(b'?').add(b'`').add(b'{').add(b'}'); + pub const PATH_SEGMENT: &AsciiSet = &PATH.add(b'/'); +} /// Query parameters #[derive(Deserialize)] @@ -213,7 +222,7 @@ pub fn directory_listing( Component::Normal(s) => { name = s.to_string_lossy().to_string(); link_accumulator - .push_str(&(utf8_percent_encode(&name, FRAGMENT).to_string() + "/")); + .push_str(&(utf8_percent_encode(&name, PATH_SEGMENT).to_string() + "/")); } _ => name = "".to_string(), }; @@ -251,13 +260,12 @@ pub fn directory_listing( for entry in dir.path.read_dir()? { if dir.is_visible(&entry) || show_hidden { let entry = entry?; - let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) => base.join(p), - Err(_) => continue, - }; // show file url as relative to static path - let file_url = utf8_percent_encode(&p.to_string_lossy(), FRAGMENT).to_string(); let file_name = entry.file_name().to_string_lossy().to_string(); + let file_url = base + .join(&utf8_percent_encode(&file_name, PATH_SEGMENT).to_string()) + .to_string_lossy() + .to_string(); // if file is a directory, add '/' to the end of the name if let Ok(metadata) = entry.metadata() { diff --git a/tests/fixtures/mod.rs b/tests/fixtures/mod.rs index 7efbe5e..1cf6c59 100644 --- a/tests/fixtures/mod.rs +++ b/tests/fixtures/mod.rs @@ -16,6 +16,9 @@ pub static FILES: &[&str] = &[ "test \" \' & < >.csv", "😀.data", "⎙.mp4", + "#[]{}()@!$&'`+,;= %20.test", + #[cfg(unix)] + ":?#[]{}<>()@!$&'`|*+,;= %20.test", ]; /// Hidden files for testing purpose diff --git a/tests/serve_request.rs b/tests/serve_request.rs index 6477bc2..25c5574 100644 --- a/tests/serve_request.rs +++ b/tests/serve_request.rs @@ -49,6 +49,12 @@ fn serves_requests_with_non_default_port(tmpdir: TempDir, port: u16) -> Result<( for &file in FILES { let f = parsed.find(|x: &Node| x.text() == file).next().unwrap(); + reqwest::blocking::get(format!( + "http://localhost:{}/{}", + port, + f.attr("href").unwrap() + ))? + .error_for_status()?; assert_eq!( format!("/{}", file), percent_encoding::percent_decode_str(f.attr("href").unwrap()).decode_utf8_lossy(), |