Skip to content

Commit

Permalink
Merge pull request #479 from aliemjay/rfc-resolve-symlinks
Browse files Browse the repository at this point in the history
Resolve symlinks on directory listing
  • Loading branch information
svenstaro authored Apr 18, 2021
2 parents 76284be + c368a11 commit a135c5d
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 45 deletions.
25 changes: 4 additions & 21 deletions data/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,6 @@ a.file:visited,
color: var(--file_link_color);
}

a.symlink,
a.symlink:visited {
color: var(--symlink_link_color);
}

a.directory:hover {
color: var(--directory_link_color);
}
Expand All @@ -95,13 +90,10 @@ a.file:hover {
color: var(--file_link_color);
}

a.symlink:hover {
color: var(--symlink_link_color);
}

.symlink-symbol {
.symlink-symbol::after {
content: "";
display: inline-block;
border: 1px solid var(--symlink_link_color);
border: 1px solid;
margin-left: 0.5rem;
border-radius: 0.2rem;
padding: 0 0.1rem;
Expand Down Expand Up @@ -488,17 +480,12 @@ th span.active span {
}

a.root,
a.file,
a.symlink {
a.file {
display: inline-block;
flex: 1;
padding: 0.5625rem 0;
}

a.symlink {
width: 100%;
}

.back {
display: flex;
}
Expand Down Expand Up @@ -539,7 +526,6 @@ th span.active span {
--text_color: #323232;
--directory_link_color: #d02474;
--file_link_color: #0086b3;
--symlink_link_color: #ed6a43;
--table_background: #ffffff;
--table_text_color: #323232;
--table_header_background: #323232;
Expand Down Expand Up @@ -584,7 +570,6 @@ th span.active span {
--text_color: #fefefe;
--directory_link_color: #03a9f4;
--file_link_color: #ea95ff;
--symlink_link_color: #ff9800;
--table_background: #353946;
--table_text_color: #eeeeee;
--table_header_background: #5294e2;
Expand Down Expand Up @@ -629,7 +614,6 @@ th span.active span {
--text_color: #efefef;
--directory_link_color: #f0dfaf;
--file_link_color: #87d6d5;
--symlink_link_color: #ffccee;
--table_background: #4a4949;
--table_text_color: #efefef;
--table_header_background: #7f9f7f;
Expand Down Expand Up @@ -674,7 +658,6 @@ th span.active span {
--text_color: #f8f8f2;
--directory_link_color: #f92672;
--file_link_color: #a6e22e;
--symlink_link_color: #fd971f;
--table_background: #3b3a32;
--table_text_color: #f8f8f0;
--table_header_background: #75715e;
Expand Down
38 changes: 18 additions & 20 deletions src/listing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ pub enum EntryType {

/// Entry is a file
File,

/// Entry is a symlink
Symlink,
}

/// Entry
Expand All @@ -87,6 +84,9 @@ pub struct Entry {
/// Type of the entry
pub entry_type: EntryType,

/// Entry is symlink. Not mutually exclusive with entry_type
pub is_symlink: bool,

/// URL of the entry
pub link: String,

Expand All @@ -101,13 +101,15 @@ impl Entry {
fn new(
name: String,
entry_type: EntryType,
is_symlink: bool,
link: String,
size: Option<bytesize::ByteSize>,
last_modification_date: Option<SystemTime>,
) -> Self {
Entry {
name,
entry_type,
is_symlink,
link,
size,
last_modification_date,
Expand All @@ -123,11 +125,6 @@ impl Entry {
pub fn is_file(&self) -> bool {
self.entry_type == EntryType::File
}

/// Returns wether the entry is a symlink
pub fn is_symlink(&self) -> bool {
self.entry_type == EntryType::Symlink
}
}

/// One entry in the path to the listed directory
Expand Down Expand Up @@ -263,41 +260,42 @@ pub fn directory_listing(
let entry = entry?;
// show file url as relative to static path
let file_name = entry.file_name().to_string_lossy().to_string();
let (is_symlink, metadata) = match entry.metadata() {
Ok(metadata) if metadata.file_type().is_symlink() => {
// for symlinks, get the metadata of the original file
(true, std::fs::metadata(entry.path()))
}
res => (false, res),
};
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() {
if skip_symlinks && metadata.file_type().is_symlink() {
if let Ok(metadata) = metadata {
if skip_symlinks && is_symlink {
continue;
}
let last_modification_date = match metadata.modified() {
Ok(date) => Some(date),
Err(_) => None,
};

if metadata.file_type().is_symlink() {
entries.push(Entry::new(
file_name,
EntryType::Symlink,
file_url,
None,
last_modification_date,
));
} else if metadata.is_dir() {
if metadata.is_dir() {
entries.push(Entry::new(
file_name,
EntryType::Directory,
is_symlink,
file_url,
None,
last_modification_date,
));
} else {
} else if metadata.is_file() {
entries.push(Entry::new(
file_name,
EntryType::File,
is_symlink,
file_url,
Some(ByteSize::b(metadata.len())),
last_modification_date,
Expand Down
10 changes: 6 additions & 4 deletions src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,22 +330,24 @@ fn entry_row(
@if entry.is_dir() {
a.directory href=(parametrized_link(&entry.link, sort_method, sort_order)) {
(entry.name) "/"
@if entry.is_symlink {
span.symlink-symbol { }
}
}
} @else if entry.is_file() {
div.file-entry {
a.file href=(&entry.link) {
(entry.name)
@if entry.is_symlink {
span.symlink-symbol { }
}
}
@if let Some(size) = entry.size {
span.mobile-info.size {
(size)
}
}
}
} @else if entry.is_symlink() {
a.symlink href=(parametrized_link(&entry.link, sort_method, sort_order)) {
(entry.name) span.symlink-symbol { "⇢" }
}
}
}
}
Expand Down
74 changes: 74 additions & 0 deletions tests/serve_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ use regex::Regex;
use rstest::rstest;
use select::document::Document;
use select::node::Node;
use std::path::Path;
use std::process::{Command, Stdio};
use std::thread::sleep;
use std::time::Duration;

#[cfg(unix)]
use std::os::unix::fs::{symlink as symlink_dir, symlink as symlink_file};
#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};

#[rstest]
fn serves_requests_with_no_options(tmpdir: TempDir) -> Result<(), Error> {
let mut child = Command::cargo_bin("miniserve")?
Expand Down Expand Up @@ -159,6 +165,74 @@ fn serves_requests_no_hidden_files_without_flag(tmpdir: TempDir, port: u16) -> R
Ok(())
}

#[rstest(no_symlinks, case(true), case(false))]
fn serves_requests_symlinks(tmpdir: TempDir, port: u16, no_symlinks: bool) -> Result<(), Error> {
let mut comm = Command::cargo_bin("miniserve")?;
comm.arg(tmpdir.path())
.arg("-p")
.arg(port.to_string())
.stdout(Stdio::null());
if no_symlinks {
comm.arg("--no-symlinks");
}

let mut child = comm.spawn()?;
sleep(Duration::from_secs(1));

let files = &["symlink-file.html"];
let dirs = &["symlink-dir/"];
let broken = &["symlink broken"];

for &directory in dirs {
let orig = Path::new(DIRECTORIES[0].strip_suffix("/").unwrap());
let link = tmpdir
.path()
.join(Path::new(directory.strip_suffix("/").unwrap()));
symlink_dir(orig, link).expect("Couldn't create symlink");
}
for &file in files {
let orig = Path::new(FILES[0]);
let link = tmpdir.path().join(Path::new(file));
symlink_file(orig, link).expect("Couldn't create symlink");
}
for &file in broken {
let orig = Path::new("should-not-exist.xxx");
let link = tmpdir.path().join(Path::new(file));
symlink_file(orig, link).expect("Couldn't create symlink");
}

let body = reqwest::blocking::get(format!("http://localhost:{}", port).as_str())?
.error_for_status()?;
let parsed = Document::from_read(body)?;

for &entry in files.into_iter().chain(dirs) {
let node = parsed
.find(|x: &Node| x.name().unwrap_or_default() == "a" && x.text() == entry)
.next();
assert_eq!(node.is_none(), no_symlinks);
if no_symlinks {
continue;
}

let node = node.unwrap();
assert_eq!(node.attr("href").unwrap().strip_prefix("/").unwrap(), entry);
reqwest::blocking::get(format!("http://localhost:{}/{}", port, entry))?
.error_for_status()?;
if entry.ends_with("/") {
assert_eq!(node.attr("class").unwrap(), "directory");
} else {
assert_eq!(node.attr("class").unwrap(), "file");
}
}
for &entry in broken {
assert!(parsed.find(|x: &Node| x.text() == entry).next().is_none());
}

child.kill()?;

Ok(())
}

#[rstest]
fn serves_requests_with_randomly_assigned_port(tmpdir: TempDir) -> Result<(), Error> {
let mut child = Command::cargo_bin("miniserve")?
Expand Down

0 comments on commit a135c5d

Please sign in to comment.