index : static-web-server.git

ascending towards madness

author Jose Quintana <1700322+joseluisq@users.noreply.github.com> 2022-10-07 0:45:50.0 +00:00:00
committer GitHub <noreply@github.com> 2022-10-07 0:45:50.0 +00:00:00
commit
3c863fdceed386969e22cf5080699c4218b55709 [patch]
tree
655eb38e4c63104970029b8ee966503815d42b05
parent
61d4bb2feccc10c2cced956afa7dcdd00b5ef01f
download
3c863fdceed386969e22cf5080699c4218b55709.tar.gz

fix: directory listing links not encoded properly (#150)

directory listing links were not encoded leading the browsers to interpret them deliberately resulting in broken links when file names use special characters.

resolves #149

Diff

 src/directory_listing.rs                                     | 18 +++---
 tests/dir_listing.rs                                         | 40 +++++++++++++-
 tests/fixtures/public/spécial directöry/spécial file.txt~ |  0
 3 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/src/directory_listing.rs b/src/directory_listing.rs
index 0e12b1c..188a35b 100644
--- a/src/directory_listing.rs
+++ b/src/directory_listing.rs
@@ -5,7 +5,7 @@ use headers::{ContentLength, ContentType, HeaderMapExt};
use humansize::{file_size_opts, FileSize};
use hyper::{Body, Method, Response, StatusCode};
use mime_guess::mime;
use percent_encoding::percent_decode_str;
use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC};
use std::cmp::Ordering;
use std::future::Future;
use std::io;
@@ -92,15 +92,16 @@ async fn read_dir_entries(
    while let Some(entry) = file_entries.next_entry().await? {
        let meta = entry.metadata().await?;

        let mut name = entry
        let name = entry
            .file_name()
            .into_string()
            .map_err(|err| anyhow::anyhow!(err.into_string().unwrap_or_default()))?;
        let mut name_encoded = utf8_percent_encode(&name, NON_ALPHANUMERIC).to_string();

        let mut filesize = 0_u64;

        if meta.is_dir() {
            name += "/";
            name_encoded += "/";
            dirs_count += 1;
        } else if meta.is_file() {
            filesize = meta.len();
@@ -108,7 +109,7 @@ async fn read_dir_entries(
        } else if meta.file_type().is_symlink() {
            let m = tokio::fs::symlink_metadata(entry.path().canonicalize()?).await?;
            if m.is_dir() {
                name += "/";
                name_encoded += "/";
                dirs_count += 1;
            } else {
                filesize = meta.len();
@@ -141,7 +142,7 @@ async fn read_dir_entries(
                }
                base_str.push('/');
            }
            base_str.push_str(&name);
            base_str.push_str(&name_encoded);
            uri = Some(base_str);
        }

@@ -152,7 +153,7 @@ async fn read_dir_entries(
                String::from("-")
            }
        };
        files_found.push((name, modified, filesize, uri));
        files_found.push((name_encoded, modified, filesize, uri));
    }

    // Check the query request uri for a sorting type. E.g https://blah/?sort=5
@@ -228,14 +229,15 @@ fn create_auto_index(
        }

        let file_uri = uri.clone().unwrap_or_else(|| file_name.to_owned());
        let file_name_decoded = percent_decode_str(file_name).decode_utf8()?.to_string();

        table_row = format!(
            "{}<tr><td><a href=\"{}\">{}</a></td><td>{}</td><td align=\"right\">{}</td></tr>",
            table_row, file_uri, file_name, file_modified, filesize_str
            table_row, file_uri, file_name_decoded, file_modified, filesize_str
        );
    }

    let current_path = percent_decode_str(base_path).decode_utf8()?.to_owned();
    let current_path = percent_decode_str(base_path).decode_utf8()?.to_string();
    let dirs_str = if dirs_count == 1 {
        "directory"
    } else {
diff --git a/tests/dir_listing.rs b/tests/dir_listing.rs
index c6694b1..d70b345 100644
--- a/tests/dir_listing.rs
+++ b/tests/dir_listing.rs
@@ -160,4 +160,44 @@ mod tests {
            }
        }
    }

    #[tokio::test]
    async fn dir_listing_links_properly_encoded() {
        for method in METHODS {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir("tests/fixtures/public/"),
                uri_path: "/",
                uri_query: None,
                dir_listing: true,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
                compression_static: false,
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["content-type"], "text/html; charset=utf-8");

                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    let body_str = std::str::from_utf8(&body).unwrap();

                    assert_eq!(
                        body_str.contains(
                            r#"<a href="sp%C3%A9cial%20direct%C3%B6ry/">spécial directöry/</a>"#
                        ),
                        method == Method::GET
                    );
                }
                Err(status) => {
                    assert!(method != Method::GET && method != Method::HEAD);
                    assert_eq!(status, StatusCode::METHOD_NOT_ALLOWED);
                }
            }
        }
    }
}
diff --git "a/tests/fixtures/public/sp\303\251cial direct\303\266ry/sp\303\251cial file.txt~" "b/tests/fixtures/public/sp\303\251cial direct\303\266ry/sp\303\251cial file.txt~"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/tests/fixtures/public/sp\303\251cial direct\303\266ry/sp\303\251cial file.txt~"