diff options
author | Martin Fischer <martin@push-f.com> | 2021-07-18 17:31:00 +0200 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-07-18 17:34:30 +0200 |
commit | ae8eade8ec0497c5ce23f5a35caba997fd2045f1 (patch) | |
tree | 24112a03ef4f594379224364f850c84fb0760c20 | |
parent | 119820d6cfc80fd3429fb828891183f66b277d46 (diff) |
refactor: use camino for UTF-8 paths
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/controller.rs | 18 | ||||
-rw-r--r-- | src/forms.rs | 6 | ||||
-rw-r--r-- | src/get_routes.rs | 28 | ||||
-rw-r--r-- | src/main.rs | 25 | ||||
-rw-r--r-- | src/post_routes.rs | 35 | ||||
-rw-r--r-- | src/shares.rs | 17 |
8 files changed, 73 insertions, 64 deletions
@@ -32,6 +32,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] +name = "camino" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" + +[[package]] name = "cc" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -220,6 +226,7 @@ dependencies = [ name = "gitpad" version = "0.1.1" dependencies = [ + "camino", "chrono", "clap", "difference", @@ -33,6 +33,7 @@ clap = "3.0.0-beta.2" chrono = "0.4" multer = "2.0.0" mime_guess = "2.0" +camino = "1" [target.'cfg(unix)'.dependencies] hyperlocal = { version = "0.8", features = ["server"] } diff --git a/src/controller.rs b/src/controller.rs index e63ef97..ba1f2de 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -129,7 +129,7 @@ impl Controller for SoloController { } fn before_write(&self, text: &str, ctx: &Context, parts: &mut Parts) -> Result<(), String> { - if let Some(ext) = ctx.path.extension().and_then(|e| e.to_str()) { + if let Some(ext) = ctx.path.extension() { validate_formats(text, ext)?; } Ok(()) @@ -419,7 +419,7 @@ impl Controller for MultiUserController { let mut ok = false; self.with_shares_cache(&ctx.repo, ctx.branch.clone(), |shares| { - if let Some(mode) = shares.find_mode(ctx.path.to_str().unwrap(), username) { + if let Some(mode) = shares.find_mode(ctx.path.as_str(), username) { ok = true; } }); @@ -434,9 +434,7 @@ impl Controller for MultiUserController { let mut ok = false; self.with_shares_cache(&ctx.repo, ctx.branch.clone(), |shares| { - if let Some(AccessMode::ReadAndWrite) = - shares.find_mode(ctx.path.to_str().unwrap(), username) - { + if let Some(AccessMode::ReadAndWrite) = shares.find_mode(ctx.path.as_str(), username) { ok = true; } }); @@ -448,7 +446,7 @@ impl Controller for MultiUserController { } fn edit_hint_html(&self, ctx: &Context) -> Option<String> { - match (ctx.branch.0.as_str(), ctx.path.to_str().unwrap()) { + match (ctx.branch.0.as_str(), ctx.path.as_str()) { (_, ".shares.txt") => { return Some(include_str!("static/help_shares.txt.html").into()); } @@ -461,7 +459,7 @@ impl Controller for MultiUserController { } fn before_write(&self, text: &str, ctx: &Context, parts: &mut Parts) -> Result<(), String> { - match (ctx.branch.0.as_str(), ctx.path.to_str().unwrap()) { + match (ctx.branch.0.as_str(), ctx.path.as_str()) { (_, ".shares.txt") => { parts.extensions.insert(parse_shares_txt(text)?); } @@ -471,7 +469,7 @@ impl Controller for MultiUserController { .insert(toml::from_str::<Identities>(text).map_err(|e| e.to_string())?); } _ => { - if let Some(ext) = ctx.path.extension().and_then(|e| e.to_str()) { + if let Some(ext) = ctx.path.extension() { validate_formats(text, ext)?; } } @@ -480,7 +478,7 @@ impl Controller for MultiUserController { } fn after_write(&self, ctx: &Context, parts: &mut Parts) { - match (ctx.branch.0.as_str(), ctx.path.to_str().unwrap()) { + match (ctx.branch.0.as_str(), ctx.path.as_str()) { (_, ".shares.txt") => { self.shares_cache .write() @@ -499,7 +497,7 @@ impl Controller for MultiUserController { if own_username != ctx.branch.0 { return None; } - let path = ctx.path.to_str().unwrap(); + let path = ctx.path.as_str(); let mut result = None; self.with_shares_cache(&ctx.repo, ctx.branch.clone(), |shares| { let mut users = HashMap::new(); diff --git a/src/forms.rs b/src/forms.rs index b987aca..0a2019c 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -1,5 +1,3 @@ -use std::os::unix::prelude::OsStrExt; - use hyper::http::request::Parts; use serde::Deserialize; use sputnik::html_escape; @@ -34,7 +32,7 @@ pub fn edit_text_form<'a, C: Controller>( } else { "Creating" }, - ctx.path.file_name().unwrap().to_str().unwrap() + ctx.path.file_name().unwrap() ), header: data .oid @@ -129,7 +127,7 @@ pub fn upload_form<'a, C: Controller>( ctx: &'a Context, parts: &Parts, ) -> Page { - let filename = ctx.path.file_name().unwrap().to_str().unwrap(); + let filename = ctx.path.file_name().unwrap(); Page { title: format!("Uploading {}", filename), // TODO: add input for commit message diff --git a/src/get_routes.rs b/src/get_routes.rs index ec46feb..95d535d 100644 --- a/src/get_routes.rs +++ b/src/get_routes.rs @@ -55,7 +55,7 @@ fn view_blob<C: Controller>( parts: &Parts, ) -> Result<Response, Error> { let mut page = Page { - title: ctx.file_name().unwrap().to_owned(), + title: ctx.path.file_name().unwrap().to_owned(), header: action_links(¶ms.action, controller, &ctx, parts), ..Default::default() }; @@ -131,7 +131,7 @@ fn log_blob<C: Controller>( ctx: Context, parts: &Parts, ) -> Result<Response, Error> { - let filename = ctx.file_name().unwrap(); + let filename = ctx.path.file_name().unwrap(); let mut page = Page { title: format!("Log for {}", filename), @@ -144,14 +144,14 @@ fn log_blob<C: Controller>( walk.push(branch_head.id())?; let mut prev_commit = branch_head; - let mut prev_blobid = Some(prev_commit.tree()?.get_path(&ctx.path)?.id()); + let mut prev_blobid = Some(prev_commit.tree()?.get_path(&ctx.path.as_ref())?.id()); let mut commits = Vec::new(); // TODO: paginate for oid in walk.flatten().skip(1) { let commit = ctx.repo.find_commit(oid)?; - if let Ok(entr) = commit.tree()?.get_path(&ctx.path) { + if let Ok(entr) = commit.tree()?.get_path(&ctx.path.as_ref()) { let blobid = entr.id(); if Some(blobid) != prev_blobid { commits.push(prev_commit); @@ -166,7 +166,7 @@ fn log_blob<C: Controller>( prev_blobid = None; } } - if prev_commit.parent_count() == 0 && prev_commit.tree()?.get_path(&ctx.path).is_ok() { + if prev_commit.parent_count() == 0 && prev_commit.tree()?.get_path(&ctx.path.as_ref()).is_ok() { // the very first commit of the branch commits.push(prev_commit); } @@ -249,11 +249,11 @@ fn diff_blob<C: Controller>( commit.parents().next() }; - let blob_id = if let Ok(entry) = commit.tree()?.get_path(&ctx.path) { + let blob_id = if let Ok(entry) = commit.tree()?.get_path(&ctx.path.as_ref()) { entry.id() } else { return Ok(Page { - title: format!("Commit for {}", ctx.file_name().unwrap()), + title: format!("Commit for {}", ctx.path.file_name().unwrap()), body: "file removed".into(), header: action_links(&action_param.action, controller, &ctx, parts), ..Default::default() @@ -262,7 +262,7 @@ fn diff_blob<C: Controller>( }; let old_blob_id = old_commit - .and_then(|p| p.tree().unwrap().get_path(&ctx.path).ok()) + .and_then(|p| p.tree().unwrap().get_path(&ctx.path.as_ref()).ok()) .map(|e| e.id()); if Some(blob_id) == old_blob_id { @@ -277,7 +277,7 @@ fn diff_blob<C: Controller>( } else { "Commit" }, - ctx.file_name().unwrap() + ctx.path.file_name().unwrap() ), header: action_links(&action_param.action, controller, &ctx, parts), ..Default::default() @@ -362,7 +362,7 @@ fn run_blob<C: Controller>( ctx: Context, parts: &Parts, ) -> Result<HyperResponse, Error> { - if ctx.path.extension().unwrap().to_str() != Some("html") { + if ctx.path.extension() != Some("html") { return Err(Error::BadRequest( "run action only available for .html files".into(), )); @@ -394,12 +394,12 @@ fn move_blob<C: Controller>( )); } - let filename = ctx.file_name().unwrap(); + let filename = ctx.path.file_name().unwrap(); return forms::move_form( filename, &forms::MoveForm { - dest: ctx.path.to_str().unwrap().to_owned(), + dest: ctx.path.as_str().to_owned(), msg: None, }, None, @@ -422,7 +422,7 @@ fn remove_blob<C: Controller>( )); } - let filename = ctx.file_name().unwrap(); + let filename = ctx.path.file_name().unwrap(); let page = Page { title: format!("Remove {}", filename), @@ -444,7 +444,7 @@ pub fn view_tree<C: Controller>( parts: &Parts, ) -> Result<Response, Error> { let mut page = Page { - title: ctx.path.to_string_lossy().to_string(), + title: ctx.path.as_str().to_string(), ..Default::default() }; diff --git a/src/main.rs b/src/main.rs index e8869c7..3383371 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +use camino::Utf8Component; +use camino::Utf8Path; +use camino::Utf8PathBuf; use clap::Clap; use controller::Controller; use git2::Commit; @@ -25,9 +28,7 @@ use sputnik::request::SputnikParts; use sputnik::response::SputnikBuilder; use std::convert::Infallible; use std::env; -use std::path::Component; use std::path::Path; -use std::path::PathBuf; use std::sync::Arc; #[cfg(unix)] @@ -331,10 +332,10 @@ async fn build_response<C: Controller>( let mut comps = Vec::new(); // prevent directory traversal attacks - for comp in Path::new(&*unsanitized_path).components() { + for comp in Utf8Path::new(&*unsanitized_path).components() { match comp { - Component::Normal(name) => comps.push(name), - Component::ParentDir => { + Utf8Component::Normal(name) => comps.push(name), + Utf8Component::ParentDir => { return Err(Error::Forbidden("path traversal is forbidden".into())) } _ => {} @@ -343,7 +344,7 @@ async fn build_response<C: Controller>( let params: ActionParam = parts.query::<ActionParam>().unwrap(); - let url_path: PathBuf = comps.iter().collect(); + let url_path: Utf8PathBuf = comps.iter().collect(); let ctx = Context { repo, @@ -367,7 +368,7 @@ async fn build_response<C: Controller>( .map(|r| r.into_commit().unwrap().tree().unwrap()); if ctx.path.components().next().is_some() { - let entr = match tree.and_then(|t| t.get_path(&ctx.path)) { + let entr = match tree.and_then(|t| t.get_path(&ctx.path.as_ref())) { Ok(entr) => entr, Err(_) => { if unsanitized_path.ends_with('/') { @@ -464,7 +465,7 @@ fn action_links<C: Controller>( pub struct Context { repo: Repository, branch: Branch, - path: PathBuf, + path: Utf8PathBuf, } impl Context { @@ -492,10 +493,6 @@ impl Context { parent_commits, ) } - - fn file_name(&self) -> Option<&str> { - self.path.file_name().and_then(|x| x.to_str()) - } } #[derive(PartialEq)] @@ -528,8 +525,8 @@ fn embed_html_as_iframe(input: &str, page: &mut Page, mode: RenderMode) { } } -fn get_renderer(path: &Path) -> Option<fn(&str, &mut Page, RenderMode)> { - match path.extension().map(|e| e.to_str().unwrap()) { +fn get_renderer(path: &Utf8Path) -> Option<fn(&str, &mut Page, RenderMode)> { + match path.extension() { Some("md") => Some(render_markdown), Some("html") => Some(embed_html_as_iframe), _ => None, diff --git a/src/post_routes.rs b/src/post_routes.rs index 1b5d615..a542060 100644 --- a/src/post_routes.rs +++ b/src/post_routes.rs @@ -66,11 +66,16 @@ fn commit_file_update<C: Controller>( let mut builder = TreeUpdateBuilder::new(); - builder.upsert(ctx.path.to_str().unwrap(), blob_id, FileMode::Blob); + builder.upsert(ctx.path.as_str(), blob_id, FileMode::Blob); let (parent_tree, parent_commits) = if let Ok(commit) = ctx.branch_head() { let parent_tree = commit.tree()?; - if parent_tree.get_path(&ctx.path).ok().map(|e| e.id()) == Some(blob_id) { + if parent_tree + .get_path(&ctx.path.as_ref()) + .ok() + .map(|e| e.id()) + == Some(blob_id) + { // nothing changed, don't create an empty commit return Err(Error::Redirect(parts.uri.path().to_string())); } @@ -90,12 +95,12 @@ fn commit_file_update<C: Controller>( &msg.filter(|m| !m.trim().is_empty()).unwrap_or_else(|| { format!( "{} {}", - if parent_tree.get_path(&ctx.path).is_ok() { + if parent_tree.get_path(&ctx.path.as_ref()).is_ok() { "edit" } else { "create" }, - ctx.path.to_str().unwrap() + ctx.path ) }), &ctx.repo.find_tree(new_tree_id)?, @@ -123,7 +128,7 @@ async fn update_blob<C: Controller>( let latest_oid = commit .tree() .unwrap() - .get_path(&ctx.path) + .get_path(&ctx.path.as_ref()) .ok() .map(|e| e.id().to_string()); @@ -219,7 +224,7 @@ async fn move_entry<C: Controller>( )); } let mut data: MoveForm = body.into_form().await?; - let filename = ctx.path.file_name().unwrap().to_str().unwrap(); + let filename = ctx.path.file_name().unwrap(); if ctx.path == Path::new(&data.dest) { return move_form( @@ -246,8 +251,8 @@ async fn move_entry<C: Controller>( } let mut builder = TreeUpdateBuilder::new(); - let entr = parent_tree.get_path(&ctx.path)?; - builder.remove(&ctx.path); + let entr = parent_tree.get_path(&ctx.path.as_ref())?; + builder.remove(&ctx.path.as_str()); builder.upsert( &data.dest, entr.id(), @@ -271,7 +276,7 @@ async fn move_entry<C: Controller>( .msg .take() .filter(|m| !m.trim().is_empty()) - .unwrap_or_else(|| format!("move {} to {}", ctx.path.to_str().unwrap(), data.dest)), + .unwrap_or_else(|| format!("move {} to {}", ctx.path, data.dest)), &ctx.repo.find_tree(new_tree_id)?, &[&parent_commit], )?; @@ -305,7 +310,7 @@ async fn remove_entry<C: Controller>( } let data: RemoveForm = body.into_form().await?; let mut builder = TreeUpdateBuilder::new(); - builder.remove(&ctx.path); + builder.remove(&ctx.path.as_str()); let parent_commit = ctx.branch_head()?; let new_tree_id = builder.create_updated(&ctx.repo, &parent_commit.tree()?)?; @@ -314,7 +319,7 @@ async fn remove_entry<C: Controller>( &data .msg .filter(|m| !m.trim().is_empty()) - .unwrap_or_else(|| format!("remove {}", ctx.path.to_str().unwrap())), + .unwrap_or_else(|| format!("remove {}", ctx.path)), &ctx.repo.find_tree(new_tree_id)?, &[&parent_commit], )?; @@ -322,7 +327,7 @@ async fn remove_entry<C: Controller>( .status(StatusCode::FOUND) .header( "location", - controller.build_url_path(&ctx.branch, ctx.path.parent().unwrap().to_str().unwrap()), + controller.build_url_path(&ctx.branch, ctx.path.parent().unwrap().as_str()), ) .body("redirecting".into()) .unwrap() @@ -344,7 +349,11 @@ async fn diff_blob<C: Controller>( let form: EditForm = body.into_form().await?; let new_text = form.text.replace("\r\n", "\n"); - let entr = ctx.branch_head()?.tree().unwrap().get_path(&ctx.path)?; + let entr = ctx + .branch_head()? + .tree() + .unwrap() + .get_path(&ctx.path.as_ref())?; let blob = ctx.repo.find_blob(entr.id()).unwrap(); let old_text = from_utf8(blob.content())?; diff --git a/src/shares.rs b/src/shares.rs index 9cfe156..4017e1f 100644 --- a/src/shares.rs +++ b/src/shares.rs @@ -1,7 +1,6 @@ -use std::{ - collections::HashMap, - path::{Component, Path, PathBuf}, -}; +use std::collections::HashMap; + +use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; /// Maps paths to access rules. #[derive(Default, Debug)] @@ -149,18 +148,18 @@ pub fn parse_shares_txt(text: &str) -> Result<Shares, String> { // normalize path let mut comps = Vec::new(); - for comp in Path::new(&*path).components() { + for comp in Utf8Path::new(&*path).components() { match comp { - Component::Normal(name) => comps.push(name), - Component::ParentDir => { + Utf8Component::Normal(name) => comps.push(name), + Utf8Component::ParentDir => { return Err(format!("line #{}: path may not contain ../", idx + 1)) } _ => {} } } - let path: PathBuf = comps.iter().collect(); - let path = path.to_str().unwrap(); + let path: Utf8PathBuf = comps.iter().collect(); + let path = path.as_str(); if let Some(stripped) = path.strip_suffix("/*") { let pr = prefix_rules.entry(stripped.to_owned()).or_default(); |