aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--src/controller.rs18
-rw-r--r--src/forms.rs6
-rw-r--r--src/get_routes.rs28
-rw-r--r--src/main.rs25
-rw-r--r--src/post_routes.rs35
-rw-r--r--src/shares.rs17
8 files changed, 73 insertions, 64 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9742a24..ac4aab6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 3679e16..31c1e0c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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(&params.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();