Skip to content

Commit 1af2cad

Browse files
committed
properly encode paths
1 parent 4a5aa78 commit 1af2cad

File tree

7 files changed

+61
-33
lines changed

7 files changed

+61
-33
lines changed

app/src/components/breadcrumbs.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::PathBuf;
22

33
use leptos::prelude::*;
44

5-
use crate::utils::os_to_string;
5+
use crate::utils::{display_os_string, encode_path};
66

77
#[component]
88
pub fn Breadcrumbs(path: Signal<PathBuf>) -> impl IntoView {
@@ -11,11 +11,11 @@ pub fn Breadcrumbs(path: Signal<PathBuf>) -> impl IntoView {
1111
path.iter()
1212
.scan(PathBuf::new(), |path, part| {
1313
path.push(part);
14-
let path = format!("/index/{}", path.display());
14+
let path = format!("/index/{}", encode_path(&path));
1515

1616
Some(view! {
1717
<li>
18-
<a href=path>{os_to_string(part)}</a>
18+
<a href=path>{display_os_string(part)}</a>
1919
</li>
2020
})
2121
})

app/src/components/file_entries.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use icon::Icon;
66
use leptos::{either::Either, prelude::*};
77
use leptos_router::components::A;
88

9-
use crate::{server::Entries, utils::format_bytes};
9+
use crate::{
10+
server::Entries,
11+
utils::{encode_path, format_bytes},
12+
};
1013

1114
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
1215
pub enum EntryType {
@@ -23,14 +26,15 @@ pub struct Entry {
2326
relative_time: String,
2427
}
2528

26-
pub fn EntryComponent(data: Entry) -> impl IntoView {
29+
#[component]
30+
pub fn EntryComponent(entry: Entry) -> impl IntoView {
2731
let Entry {
2832
type_,
2933
href,
3034
name,
3135
size,
3236
relative_time,
33-
} = data;
37+
} = entry;
3438

3539
let inner = view! {
3640
<div class="grid gap-2 w-full entry grid-cols-(--entry-cols-mobile) md:grid-cols-(--entry-cols)">
@@ -74,7 +78,7 @@ pub fn FileEntries(path: Signal<PathBuf>, entries: Entries) -> impl IntoView {
7478
last_modified,
7579
} => Entry {
7680
type_: EntryType::File,
77-
href: format!("/files/{}", path.join(&name).display()),
81+
href: format!("/files/{}", encode_path(path.join(&name))),
7882
name: name.clone(),
7983
size: Some(format_bytes(size)),
8084
relative_time: last_modified.humanize(),
@@ -84,7 +88,7 @@ pub fn FileEntries(path: Signal<PathBuf>, entries: Entries) -> impl IntoView {
8488
last_modified,
8589
} => Entry {
8690
type_: EntryType::Folder,
87-
href: format!("/index/{}", path.join(&name).display()),
91+
href: format!("/index/{}", encode_path(path.join(&name))),
8892
name: name.clone(),
8993
size: None,
9094
relative_time: last_modified.humanize(),
@@ -94,7 +98,11 @@ pub fn FileEntries(path: Signal<PathBuf>, entries: Entries) -> impl IntoView {
9498

9599
entries.sort_unstable();
96100

97-
Either::Right(
98-
view! { <div class="file-view">{entries.into_iter().map(EntryComponent).collect_view()}</div> },
99-
)
101+
Either::Right(view! {
102+
<div class="file-view">
103+
<For each=move || entries.clone() key=|entry| entry.href.clone() let:entry>
104+
<EntryComponent entry=entry />
105+
</For>
106+
</div>
107+
})
100108
}

app/src/components/folder_download.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ use std::path::PathBuf;
22

33
use leptos::prelude::*;
44

5+
use crate::utils::display_os_string;
6+
57
#[component]
68
pub fn FolderDownloads(path: Signal<PathBuf>) -> impl IntoView {
79
let method_list = move || {
8-
let display_path = path.with(|path| path.display().to_string());
10+
let path = path.with(|path| display_os_string(path));
911
["zip", "tar", "tar.gz", "tar.zst"].map(|method| {
10-
view! {
11-
<li>
12-
<a href=format!("/archive/{display_path}?method={method}") class="px-3 min-w-20" download>
13-
{method}
14-
</a>
15-
</li>
16-
}
17-
})
12+
view! {
13+
<li>
14+
<a href=format!("/archive/{path}?method={method}") class="px-3 min-w-20" download>
15+
{method}
16+
</a>
17+
</li>
18+
}
19+
})
1820
};
1921

2022
view! {

app/src/components/new_folder.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::PathBuf;
22

33
use leptos::{html::Input, prelude::*};
44

5-
use crate::{server::NewFolder, utils::os_to_string};
5+
use crate::{server::NewFolder, utils::display_os_string};
66

77
#[component]
88
pub fn NewFolderButton(path: Signal<PathBuf>, action: ServerAction<NewFolder>) -> impl IntoView {
@@ -33,11 +33,7 @@ pub fn NewFolderButton(path: Signal<PathBuf>, action: ServerAction<NewFolder>) -
3333
name="name"
3434
autofocus
3535
/>
36-
<input
37-
type="hidden"
38-
name="path"
39-
value=move || os_to_string(path.read().as_os_str())
40-
/>
36+
<input type="hidden" name="path" value=move || path.with(|path| display_os_string(path)) />
4137
<div class="modal-action">
4238
<button class="btn" type="reset" onclick="new_folder_modal.close()">
4339
Cancel

app/src/server.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ pub async fn list_dir(path: PathBuf) -> Result<Entries, ServerFnError> {
5454
let mut directory = fs::read_dir(path).await?;
5555

5656
while let Some(entry) = directory.next_entry().await? {
57-
let name = entry.file_name().to_string_lossy().into_owned();
57+
let name = entry
58+
.file_name()
59+
.into_string()
60+
.expect("Filename is valid UTF-8");
5861
let metadata = entry.metadata().await?;
5962
let last_modified = metadata.modified()?.into();
6063

app/src/utils.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::time::{self, UNIX_EPOCH};
1+
use std::{
2+
borrow::Cow,
3+
time::{self, UNIX_EPOCH},
4+
};
25

36
use chrono::{DateTime, TimeZone, Utc};
47
use chrono_humanize::Humanize;
@@ -49,10 +52,18 @@ impl SystemTime {
4952

5053
use std::ffi::OsStr;
5154

52-
pub fn os_to_string(str: impl AsRef<OsStr>) -> String {
55+
pub fn display_os_string(str: impl AsRef<OsStr>) -> String {
5356
str.as_ref().to_string_lossy().to_string()
5457
}
5558

59+
pub fn encode_path(path: impl AsRef<OsStr>) -> String {
60+
urlencoding::encode(path.as_ref().to_string_lossy().as_ref()).into_owned()
61+
}
62+
63+
pub fn try_decode_path(path: &str) -> Cow<'_, str> {
64+
urlencoding::decode(path).unwrap_or(Cow::Borrowed(path))
65+
}
66+
5667
#[allow(clippy::cast_possible_truncation)]
5768
#[allow(clippy::cast_possible_wrap)]
5869
#[allow(clippy::cast_sign_loss)]

server/src/fileserv.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use axum::{
99
http::{HeaderValue, Request, StatusCode, Uri, header},
1010
response::IntoResponse,
1111
};
12-
use file_share_app::{AppConfig, AppState, shell, utils::format_bytes};
12+
use file_share_app::{
13+
AppConfig, AppState, shell,
14+
utils::{format_bytes, try_decode_path},
15+
};
1316
use leptos::{logging, prelude::provide_context};
1417
use rust_embed::RustEmbed;
1518
use tokio::io::AsyncWriteExt;
@@ -56,7 +59,12 @@ pub async fn handle_archive_with_path<'a>(
5659
Query(params): Query<HashMap<String, String>>,
5760
) -> impl IntoResponse + use<'a> {
5861
logging::log!("Handling archive with path '{path:?}' and params '{params:?}'");
59-
handle_archive(target_dir, params.get("method"), path).await
62+
handle_archive(
63+
target_dir,
64+
params.get("method"),
65+
try_decode_path(&path).as_ref(),
66+
)
67+
.await
6068
}
6169

6270
/// Handles archive requests.
@@ -66,14 +74,14 @@ pub async fn handle_archive_without_path(
6674
Query(params): Query<HashMap<String, String>>,
6775
) -> impl IntoResponse + use<> {
6876
logging::log!("Handling archive without path and with params '{params:?}'");
69-
handle_archive(target_dir, params.get("method"), String::new()).await
77+
handle_archive(target_dir, params.get("method"), "").await
7078
}
7179

7280
#[allow(clippy::unused_async)] // has to be in an async context, but doesn't await directly
7381
async fn handle_archive(
7482
base_dir: PathBuf,
7583
method: Option<&String>,
76-
path: String,
84+
path: &str,
7785
) -> impl IntoResponse + use<> {
7886
let method = method.map_or_else(Default::default, String::as_str);
7987

0 commit comments

Comments
 (0)