aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-06-24 19:04:23 +0200
committerMartin Fischer <martin@push-f.com>2021-06-24 19:41:05 +0200
commit26298bcd7ef204db4396ca2d0e603fc183220cd2 (patch)
tree9478019c3a365cb71b7cc41e12885ad25d7592b5 /src
parentd49d835e63ec654e3a5bf75b3b365354460382e8 (diff)
refactor: simplify Page and Context structs
Previously the Page struct contained references to the Controller and the http::request::Parts, so that page.render() could call controller.user_info_html(parts). This commit removes these references from the Page struct, so that it can implement Default in the future. The Context struct needs to be moved around since it contains git2::Repository, which isn't Send. Previously the Context struct also contained the http::request::Parts, so they were moved along. This commit extracts Parts out of the Context struct, so that our service function can access Parts after invoking our build_request method, allowing us to easily log request details for errors in the future.
Diffstat (limited to 'src')
-rw-r--r--src/controller.rs78
-rw-r--r--src/forms.rs22
-rw-r--r--src/get_routes.rs77
-rw-r--r--src/main.rs124
-rw-r--r--src/post_routes.rs91
5 files changed, 220 insertions, 172 deletions
diff --git a/src/controller.rs b/src/controller.rs
index 2d8b73f..f912d74 100644
--- a/src/controller.rs
+++ b/src/controller.rs
@@ -36,7 +36,7 @@ pub trait Controller {
}
/// Returns an HTML string describing who has access to the context.
- fn access_info_html(&self, ctx: &Context) -> Option<String> {
+ fn access_info_html(&self, ctx: &Context, parts: &Parts) -> Option<String> {
None
}
@@ -44,27 +44,33 @@ pub trait Controller {
fn signature(&self, repo: &Repository, parts: &Parts) -> Result<Signature, Error>;
/// Returns whether or not a request is authorized to read a file or list a directory.
- fn may_read_path(&self, context: &Context) -> bool;
+ fn may_read_path(&self, ctx: &Context, parts: &Parts) -> bool;
/// Returns whether or not a request is authorized to write a file.
- fn may_write_path(&self, context: &Context) -> bool;
+ fn may_write_path(&self, context: &Context, parts: &Parts) -> bool;
/// Returns whether or not a request is authorized to (re)move a file.
- fn may_move_path(&self, context: &Context) -> bool;
+ fn may_move_path(&self, context: &Context, parts: &Parts) -> bool;
fn edit_hint_html(&self, context: &Context) -> Option<String> {
None
}
- fn before_return_tree_page(&self, page: &mut Page, tree: Option<Tree>, context: &Context);
+ fn before_return_tree_page(
+ &self,
+ page: &mut Page,
+ tree: Option<Tree>,
+ context: &Context,
+ parts: &Parts,
+ );
/// Executed before writing a file. Return an error to abort the writing process.
- fn before_write(&self, text: &str, context: &mut Context) -> Result<(), String> {
+ fn before_write(&self, text: &str, ctx: &Context, parts: &mut Parts) -> Result<(), String> {
Ok(())
}
/// Executed after successfully writing a file.
- fn after_write(&self, context: &mut Context) {}
+ fn after_write(&self, context: &Context, parts: &mut Parts) {}
}
pub struct SoloController(pub Branch);
@@ -98,25 +104,31 @@ impl Controller for SoloController {
repo.signature().map_err(|e| Error::Internal(e.to_string()))
}
- fn may_read_path(&self, _context: &Context) -> bool {
+ fn may_read_path(&self, ctx: &Context, parts: &Parts) -> bool {
true
}
- fn may_write_path(&self, _context: &Context) -> bool {
+ fn may_write_path(&self, _context: &Context, parts: &Parts) -> bool {
true
}
- fn may_move_path(&self, _context: &Context) -> bool {
+ fn may_move_path(&self, _context: &Context, parts: &Parts) -> bool {
true
}
- fn before_return_tree_page(&self, page: &mut Page, tree: Option<Tree>, context: &Context) {
+ fn before_return_tree_page(
+ &self,
+ page: &mut Page,
+ tree: Option<Tree>,
+ context: &Context,
+ parts: &Parts,
+ ) {
if tree.map(|t| t.len()).unwrap_or_default() == 0 {
page.body.push_str("<p>create files by editing the URL, e.g. <a href='/hello-world.md'>/hello-world.md</a></p>");
}
}
- fn before_write(&self, text: &str, ctx: &mut Context) -> Result<(), String> {
+ 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()) {
validate_formats(text, ext)?;
}
@@ -274,8 +286,6 @@ fn multi_user_startpage(
// TODO: add domain name to title?
let mut page = Page {
title: "GitPad".into(),
- controller,
- parts: &parts,
body: String::new(),
header: None,
};
@@ -348,8 +358,6 @@ impl Controller for MultiUserController {
title: "".into(),
header: None,
body: String::new(),
- controller: self,
- parts,
};
self.list_shares(repo, &rev, username, &mut page.body);
@@ -371,8 +379,14 @@ impl Controller for MultiUserController {
))
}
- fn before_return_tree_page(&self, page: &mut Page, tree: Option<Tree>, context: &Context) {
- let username = username_from_parts(&context.parts).unwrap();
+ fn before_return_tree_page(
+ &self,
+ page: &mut Page,
+ tree: Option<Tree>,
+ context: &Context,
+ parts: &Parts,
+ ) {
+ let username = username_from_parts(&parts).unwrap();
if context.path.components().count() == 0 {
if context.branch.0 == username {
match tree {
@@ -405,8 +419,8 @@ impl Controller for MultiUserController {
}
}
- fn may_read_path(&self, ctx: &Context) -> bool {
- let username = username_from_parts(&ctx.parts).unwrap();
+ fn may_read_path(&self, ctx: &Context, parts: &Parts) -> bool {
+ let username = username_from_parts(parts).unwrap();
if ctx.branch.0 == username {
return true;
}
@@ -420,8 +434,8 @@ impl Controller for MultiUserController {
ok
}
- fn may_write_path(&self, ctx: &Context) -> bool {
- let username = username_from_parts(&ctx.parts).unwrap();
+ fn may_write_path(&self, ctx: &Context, parts: &Parts) -> bool {
+ let username = username_from_parts(&parts).unwrap();
if ctx.branch.0 == username {
return true;
}
@@ -437,8 +451,8 @@ impl Controller for MultiUserController {
ok
}
- fn may_move_path(&self, ctx: &Context) -> bool {
- ctx.branch.0 == username_from_parts(&ctx.parts).unwrap()
+ fn may_move_path(&self, ctx: &Context, parts: &Parts) -> bool {
+ ctx.branch.0 == username_from_parts(&parts).unwrap()
}
fn edit_hint_html(&self, ctx: &Context) -> Option<String> {
@@ -454,13 +468,13 @@ impl Controller for MultiUserController {
None
}
- fn before_write(&self, text: &str, ctx: &mut Context) -> Result<(), String> {
+ fn before_write(&self, text: &str, ctx: &Context, parts: &mut Parts) -> Result<(), String> {
match (ctx.branch.0.as_str(), ctx.path.to_str().unwrap()) {
(_, ".shares.txt") => {
- ctx.parts.extensions.insert(parse_shares_txt(text)?);
+ parts.extensions.insert(parse_shares_txt(text)?);
}
("gitpad", "users.toml") => {
- ctx.parts
+ parts
.extensions
.insert(toml::from_str::<Identities>(text).map_err(|e| e.to_string())?);
}
@@ -473,23 +487,23 @@ impl Controller for MultiUserController {
Ok(())
}
- fn after_write(&self, ctx: &mut Context) {
+ fn after_write(&self, ctx: &Context, parts: &mut Parts) {
match (ctx.branch.0.as_str(), ctx.path.to_str().unwrap()) {
(_, ".shares.txt") => {
self.shares_cache
.write()
.unwrap()
- .insert(ctx.branch.clone(), ctx.parts.extensions.remove().unwrap());
+ .insert(ctx.branch.clone(), parts.extensions.remove().unwrap());
}
("gitpad", "users.toml") => {
- *self.identities.write().unwrap() = ctx.parts.extensions.remove().unwrap();
+ *self.identities.write().unwrap() = parts.extensions.remove().unwrap();
}
_ => {}
}
}
- fn access_info_html(&self, ctx: &Context) -> Option<String> {
- let own_username = username_from_parts(&ctx.parts).unwrap();
+ fn access_info_html(&self, ctx: &Context, parts: &Parts) -> Option<String> {
+ let own_username = username_from_parts(&parts).unwrap();
if own_username != ctx.branch.0 {
return None;
}
diff --git a/src/forms.rs b/src/forms.rs
index fc16d09..240d300 100644
--- a/src/forms.rs
+++ b/src/forms.rs
@@ -1,3 +1,4 @@
+use hyper::http::request::Parts;
use serde::Deserialize;
use sputnik::html_escape;
@@ -19,7 +20,8 @@ pub fn edit_text_form<'a, C: Controller>(
error: Option<&str>,
controller: &'a C,
ctx: &'a Context,
-) -> Page<'a> {
+ parts: &Parts,
+) -> Page {
let mut page = Page {
title: format!(
"{} {}",
@@ -33,12 +35,10 @@ pub fn edit_text_form<'a, C: Controller>(
header: data
.oid
.is_some()
- .then(|| action_links("edit", controller, ctx)),
+ .then(|| action_links("edit", controller, ctx, parts)),
body: String::new(),
- controller,
- parts: &ctx.parts,
};
- if let Some(access_info_html) = controller.access_info_html(&ctx) {
+ if let Some(access_info_html) = controller.access_info_html(&ctx, parts) {
page.body.push_str(&access_info_html);
}
if let Some(hint_html) = controller.edit_hint_html(ctx) {
@@ -90,13 +90,12 @@ pub fn move_form<C: Controller>(
error: Option<&str>,
controller: &C,
ctx: &Context,
+ parts: &Parts,
) -> Result<Response, Error> {
let mut page = Page {
title: format!("Move {}", filename),
- controller,
- parts: &ctx.parts,
body: String::new(),
- header: Some(action_links("move", controller, ctx)),
+ header: Some(action_links("move", controller, ctx, parts)),
};
if let Some(error) = error {
@@ -120,7 +119,8 @@ pub fn upload_form<'a, C: Controller>(
file_exists: bool,
controller: &'a C,
ctx: &'a Context,
-) -> Page<'a> {
+ parts: &Parts,
+) -> Page {
let filename = ctx.path.file_name().unwrap().to_str().unwrap();
Page {
title: format!("Uploading {}", filename),
@@ -130,8 +130,6 @@ pub fn upload_form<'a, C: Controller>(
<button>Upload</button>\
</form>"
.into(),
- header: file_exists.then(|| action_links("edit", controller, &ctx)),
- controller,
- parts: &ctx.parts,
+ header: file_exists.then(|| action_links("edit", controller, &ctx, parts)),
}
}
diff --git a/src/get_routes.rs b/src/get_routes.rs
index ff86869..3661896 100644
--- a/src/get_routes.rs
+++ b/src/get_routes.rs
@@ -7,6 +7,7 @@ use git2::Repository;
use git2::Tree;
use git2::TreeEntry;
use hyper::header;
+use hyper::http::request::Parts;
use hyper::http::response::Builder;
use hyper::StatusCode;
use serde::Deserialize;
@@ -22,6 +23,7 @@ use crate::get_renderer;
use crate::ActionParam;
use crate::Context;
use crate::Error;
+use crate::HyperResponse;
use crate::Page;
use crate::Response;
@@ -30,16 +32,17 @@ pub(crate) fn get_blob<C: Controller>(
params: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
match params.action.as_ref() {
- "view" => view_blob(entr, params, controller, ctx),
- "edit" => edit_blob(entr, params, controller, ctx),
- "upload" => Ok(forms::upload_form(true, controller, &ctx).into()),
- "log" => log_blob(entr, params, controller, ctx),
- "diff" => diff_blob(entr, params, controller, ctx),
- "raw" => raw_blob(entr, params, controller, ctx),
- "move" => move_blob(entr, params, controller, ctx),
- "remove" => remove_blob(entr, params, controller, ctx),
+ "view" => view_blob(entr, params, controller, ctx, parts),
+ "edit" => edit_blob(entr, params, controller, ctx, parts),
+ "upload" => Ok(forms::upload_form(true, controller, &ctx, parts).into()),
+ "log" => log_blob(entr, params, controller, ctx, parts),
+ "diff" => diff_blob(entr, params, controller, ctx, parts),
+ "raw" => raw_blob(entr, params, controller, ctx, parts),
+ "move" => move_blob(entr, params, controller, ctx, parts),
+ "remove" => remove_blob(entr, params, controller, ctx, parts),
_ => Err(Error::BadRequest("unknown action".into())),
}
}
@@ -49,16 +52,15 @@ fn view_blob<C: Controller>(
params: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
let mut page = Page {
title: ctx.file_name().unwrap().to_owned(),
body: String::new(),
- header: Some(action_links(&params.action, controller, &ctx)),
- controller,
- parts: &ctx.parts,
+ header: Some(action_links(&params.action, controller, &ctx, parts)),
};
- if let Some(access_info_html) = controller.access_info_html(&ctx) {
+ if let Some(access_info_html) = controller.access_info_html(&ctx, parts) {
page.body.push_str(&access_info_html);
}
@@ -96,8 +98,9 @@ fn edit_blob<C: Controller>(
_params: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if !controller.may_write_path(&ctx) {
+ if !controller.may_write_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to edit this file".into(),
));
@@ -113,10 +116,11 @@ fn edit_blob<C: Controller>(
None,
controller,
&ctx,
+ parts,
)
.into());
} else {
- return Ok(forms::upload_form(true, controller, &ctx).into());
+ Ok(forms::upload_form(true, controller, &ctx, parts).into())
}
}
@@ -125,15 +129,14 @@ fn log_blob<C: Controller>(
params: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
let filename = ctx.file_name().unwrap();
let mut page = Page {
title: format!("Log for {}", filename),
body: String::new(),
- header: Some(action_links(&params.action, controller, &ctx)),
- controller,
- parts: &ctx.parts,
+ header: Some(action_links(&params.action, controller, &ctx, parts)),
};
let mut walk = ctx.repo.revwalk()?;
@@ -221,8 +224,9 @@ fn diff_blob<C: Controller>(
action_param: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- let params: DiffParams = ctx.parts.query()?;
+ let params: DiffParams = parts.query()?;
let branch_commit = ctx.branch_head()?;
let mut commit = find_commit(&ctx.repo, &params.id, &branch_commit.id())
@@ -251,9 +255,7 @@ fn diff_blob<C: Controller>(
return Ok(Page {
title: format!("Commit for {}", ctx.file_name().unwrap()),
body: "file removed".into(),
- header: Some(action_links(&action_param.action, controller, &ctx)),
- controller,
- parts: &ctx.parts,
+ header: Some(action_links(&action_param.action, controller, &ctx, parts)),
}
.into());
};
@@ -276,10 +278,8 @@ fn diff_blob<C: Controller>(
},
ctx.file_name().unwrap()
),
- header: Some(action_links(&action_param.action, controller, &ctx)),
+ header: Some(action_links(&action_param.action, controller, &ctx, parts)),
body: String::new(),
- controller,
- parts: &ctx.parts,
};
page.body.push_str("<div>");
if params.oldid.is_none() {
@@ -312,9 +312,9 @@ fn raw_blob<C: Controller>(
_params: ActionParam,
_controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if let Some(etag) = ctx
- .parts
+ if let Some(etag) = parts
.headers
.get(header::IF_NONE_MATCH)
.and_then(|v| v.to_str().ok())
@@ -323,12 +323,13 @@ fn raw_blob<C: Controller>(
return Ok(Builder::new()
.status(StatusCode::NOT_MODIFIED)
.body("".into())
- .unwrap());
+ .unwrap()
+ .into());
}
}
let blob = ctx.repo.find_blob(entr.id()).unwrap();
- let mut resp = Response::new(blob.content().to_owned().into());
+ let mut resp = HyperResponse::new(blob.content().to_owned().into());
resp.headers_mut()
.insert(header::ETAG, format!("\"{}\"", entr.id()).parse().unwrap());
@@ -346,7 +347,7 @@ fn raw_blob<C: Controller>(
.insert(header::CONTENT_TYPE, mime.to_string().parse().unwrap());
}
}
- Ok(resp)
+ Ok(resp.into())
}
fn move_blob<C: Controller>(
@@ -354,8 +355,9 @@ fn move_blob<C: Controller>(
_params: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if !controller.may_move_path(&ctx) {
+ if !controller.may_move_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to move this file".into(),
));
@@ -372,6 +374,7 @@ fn move_blob<C: Controller>(
None,
controller,
&ctx,
+ parts,
);
}
@@ -380,8 +383,9 @@ fn remove_blob<C: Controller>(
params: ActionParam,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if !controller.may_move_path(&ctx) {
+ if !controller.may_move_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to remove this file".into(),
));
@@ -391,9 +395,7 @@ fn remove_blob<C: Controller>(
let page = Page {
title: format!("Remove {}", filename),
- controller,
- parts: &ctx.parts,
- header: Some(action_links(&params.action, controller, &ctx)),
+ header: Some(action_links(&params.action, controller, &ctx, parts)),
body: "<form method=post autocomplete=off>\
<label>Message <input name=msg autofocus></label>\
<button>Remove</button></form>"
@@ -407,17 +409,16 @@ pub fn view_tree<C: Controller>(
tree: Result<Tree, git2::Error>,
controller: &C,
ctx: &Context,
+ parts: &Parts,
) -> Result<Response, Error> {
let mut page = Page {
title: ctx.path.to_string_lossy().to_string(),
- controller,
- parts: &ctx.parts,
body: String::new(),
header: None,
};
page.body.push_str("<ul>");
- if ctx.parts.uri.path() != "/" {
+ if parts.uri.path() != "/" {
page.body
.push_str("<li><a href='..' title='go to parent directory'>../</a></li>");
}
@@ -445,7 +446,7 @@ pub fn view_tree<C: Controller>(
}
page.body.push_str("</ul>");
- controller.before_return_tree_page(&mut page, tree.ok(), ctx);
+ controller.before_return_tree_page(&mut page, tree.ok(), ctx, parts);
Ok(page.into())
}
diff --git a/src/main.rs b/src/main.rs
index 276a3e2..7313a6d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -48,7 +48,24 @@ mod get_routes;
mod post_routes;
mod shares;
-pub(crate) type Response = hyper::Response<hyper::Body>;
+pub enum Response {
+ Raw(HyperResponse),
+ Page(Page),
+}
+
+impl From<Page> for Response {
+ fn from(page: Page) -> Self {
+ Self::Page(page)
+ }
+}
+
+impl From<HyperResponse> for Response {
+ fn from(resp: HyperResponse) -> Self {
+ Self::Raw(resp)
+ }
+}
+
+pub(crate) type HyperResponse = hyper::Response<hyper::Body>;
pub(crate) type Request = hyper::Request<hyper::Body>;
#[derive(Clap, Debug)]
@@ -194,11 +211,18 @@ async fn service<C: Controller>(
controller: Arc<C>,
args: Arc<Args>,
request: Request,
-) -> Result<Response, Infallible> {
- let (parts, body) = request.into_parts();
+) -> Result<HyperResponse, Infallible> {
+ let (mut parts, body) = request.into_parts();
- let mut resp = build_response(args, &*controller, parts, body)
+ let mut resp = build_response(args, &*controller, &mut parts, body)
.await
+ .map(|resp| match resp {
+ Response::Raw(resp) => resp,
+ Response::Page(page) => Builder::new()
+ .content_type(mime::TEXT_HTML)
+ .body(render_page(&page, &*controller, &parts).into())
+ .unwrap(),
+ })
.unwrap_or_else(|err| {
let (status, message) = match err {
Error::BadRequest(msg) => (400, msg),
@@ -243,48 +267,35 @@ async fn service<C: Controller>(
Ok(resp)
}
-pub struct Page<'a> {
+pub struct Page {
title: String,
header: Option<String>,
body: String,
- controller: &'a dyn Controller,
- parts: &'a Parts,
-}
-
-impl From<Page<'_>> for Response {
- fn from(page: Page) -> Self {
- Builder::new()
- .content_type(mime::TEXT_HTML)
- .body(page.render().into())
- .unwrap()
- }
}
const CSS: &str = include_str!("static/style.css");
-impl Page<'_> {
- fn render(&self) -> String {
- format!(
- "<!doctype html>\
- <html>\
- <head>\
- <meta charset=utf-8>\
- <title>{}</title>\
- <meta name=viewport content=\"width=device-width, initial-scale=1\">\
- <style>{}</style>\
- </head>\
- <body><header id=header>{}{}</header>{}</body></html>\
- ",
- html_escape(&self.title),
- CSS,
- self.header.as_deref().unwrap_or_default(),
- self.controller
- .user_info_html(self.parts)
- .map(|h| format!("<div class=user-info>{}</div>", h))
- .unwrap_or_default(),
- self.body,
- )
- }
+fn render_page<C: Controller>(page: &Page, controller: &C, parts: &Parts) -> String {
+ format!(
+ "<!doctype html>\
+ <html>\
+ <head>\
+ <meta charset=utf-8>\
+ <title>{}</title>\
+ <meta name=viewport content=\"width=device-width, initial-scale=1\">\
+ <style>{}</style>\
+ </head>\
+ <body><header id=header>{}{}</header>{}</body></html>\
+ ",
+ html_escape(&page.title),
+ CSS,
+ page.header.as_deref().unwrap_or_default(),
+ controller
+ .user_info_html(parts)
+ .map(|h| format!("<div class=user-info>{}</div>", h))
+ .unwrap_or_default(),
+ page.body,
+ )
}
#[derive(Deserialize)]
@@ -309,7 +320,7 @@ impl Branch {
async fn build_response<C: Controller>(
args: Arc<Args>,
controller: &C,
- parts: Parts,
+ parts: &mut Parts,
body: Body,
) -> Result<Response, Error> {
let unsanitized_path = percent_decode_str(parts.uri.path())
@@ -346,17 +357,16 @@ async fn build_response<C: Controller>(
repo,
path: url_path,
branch: rev,
- parts,
};
- if !controller.may_read_path(&ctx) {
+ if !controller.may_read_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to view this file".into(),
));
}
- if ctx.parts.method == Method::POST {
- return post_routes::build_response(&args, &params, controller, ctx, body).await;
+ if parts.method == Method::POST {
+ return post_routes::build_response(&args, &params, controller, ctx, body, parts).await;
}
let mut tree = ctx
@@ -372,17 +382,18 @@ async fn build_response<C: Controller>(
return Err(Error::NotFound("directory not found".into()));
}
- if controller.may_write_path(&ctx) {
+ if controller.may_write_path(&ctx, parts) {
if params.action == "edit" {
return Ok(forms::edit_text_form(
&forms::EditForm::default(),
None,
controller,
&ctx,
+ parts,
)
.into());
} else if params.action == "upload" {
- return Ok(forms::upload_form(false, controller, &ctx).into());
+ return Ok(forms::upload_form(false, controller, &ctx, parts).into());
} else {
return Err(Error::NotFound(
"file not found, but <a href=?action=edit>you can write it</a> or <a href=?action=upload>upload it</a>".into(),
@@ -404,18 +415,19 @@ async fn build_response<C: Controller>(
.build_url_path(&ctx.branch, unsanitized_path.trim_end_matches('/')),
)
.body("redirecting".into())
- .unwrap());
+ .unwrap()
+ .into());
}
- return get_routes::get_blob(entr, params, controller, ctx);
+ return get_routes::get_blob(entr, params, controller, ctx, &parts);
}
tree = ctx.repo.find_tree(entr.id());
if !unsanitized_path.ends_with('/') {
- return Err(Error::MissingTrailingSlash(ctx.parts.uri.path().to_owned()));
+ return Err(Error::MissingTrailingSlash(parts.uri.path().to_owned()));
}
}
- get_routes::view_tree(tree, controller, &ctx)
+ get_routes::view_tree(tree, controller, &ctx, &parts)
}
fn render_link(name: &str, label: &str, active_action: &str) -> String {
@@ -435,18 +447,23 @@ fn render_link(name: &str, label: &str, active_action: &str) -> String {
)
}
-fn action_links<C: Controller>(active_action: &str, controller: &C, ctx: &Context) -> String {
+fn action_links<C: Controller>(
+ active_action: &str,
+ controller: &C,
+ ctx: &Context,
+ parts: &Parts,
+) -> String {
let mut out = String::new();
out.push_str("<div class=actions>");
out.push_str("<a href=. title='list parent directory'>ls</a>");
out.push_str(&render_link("view", "view", active_action));
- if controller.may_write_path(ctx) {
+ if controller.may_write_path(ctx, parts) {
out.push_str(&render_link("edit", "edit", active_action));
}
out.push_str(&render_link("log", "log", active_action));
out.push_str(&render_link("raw", "raw", active_action));
- if controller.may_move_path(ctx) {
+ if controller.may_move_path(ctx, parts) {
out.push_str(&render_link("move", "mv", active_action));
out.push_str(&render_link("remove", "rm", active_action));
}
@@ -456,7 +473,6 @@ fn action_links<C: Controller>(active_action: &str, controller: &C, ctx: &Contex
pub struct Context {
repo: Repository,
- parts: Parts,
branch: Branch,
path: PathBuf,
}
diff --git a/src/post_routes.rs b/src/post_routes.rs
index 0374cae..8cbd3b2 100644
--- a/src/post_routes.rs
+++ b/src/post_routes.rs
@@ -1,6 +1,7 @@
use git2::build::TreeUpdateBuilder;
use git2::FileMode;
use hyper::header;
+use hyper::http::request::Parts;
use hyper::http::response::Builder;
use hyper::Body;
use hyper::StatusCode;
@@ -28,10 +29,10 @@ pub(crate) async fn build_response<C: Controller>(
controller: &C,
ctx: Context,
body: Body,
+ parts: &mut Parts,
) -> Result<Response, Error> {
if let Some(ref enforced_origin) = args.origin {
- if ctx
- .parts
+ if parts
.headers
.get(header::ORIGIN)
.filter(|h| h.as_bytes() == enforced_origin.as_bytes())
@@ -44,12 +45,12 @@ pub(crate) async fn build_response<C: Controller>(
}
}
match params.action.as_ref() {
- "edit" => return update_blob(body, controller, ctx).await,
- "upload" => return upload_blob(body, controller, ctx).await,
- "move" => return move_entry(body, controller, ctx).await,
- "remove" => return remove_entry(body, controller, ctx).await,
- "diff" => return diff_blob(body, controller, ctx).await,
- "preview" => return preview_edit(body, controller, ctx).await,
+ "edit" => return update_blob(body, controller, ctx, parts).await,
+ "upload" => return upload_blob(body, controller, ctx, parts).await,
+ "move" => return move_entry(body, controller, ctx, parts).await,
+ "remove" => return remove_entry(body, controller, ctx, parts).await,
+ "diff" => return diff_blob(body, controller, ctx, parts).await,
+ "preview" => return preview_edit(body, controller, ctx, parts).await,
_ => return Err(Error::BadRequest("unknown POST action".into())),
}
}
@@ -59,6 +60,7 @@ fn commit_file_update<C: Controller>(
msg: Option<String>,
controller: &C,
ctx: &Context,
+ parts: &Parts,
) -> Result<(), Error> {
let blob_id = ctx.repo.blob(data)?;
@@ -70,7 +72,7 @@ fn commit_file_update<C: Controller>(
let parent_tree = commit.tree()?;
if parent_tree.get_path(&ctx.path).ok().map(|e| e.id()) == Some(blob_id) {
// nothing changed, don't create an empty commit
- return Err(Error::Redirect(ctx.parts.uri.path().to_string()));
+ return Err(Error::Redirect(parts.uri.path().to_string()));
}
(parent_tree, vec![commit])
} else {
@@ -82,7 +84,7 @@ fn commit_file_update<C: Controller>(
let new_tree_id = builder.create_updated(&ctx.repo, &parent_tree)?;
- let signature = controller.signature(&ctx.repo, &ctx.parts)?;
+ let signature = controller.signature(&ctx.repo, &parts)?;
ctx.commit(
&signature,
&msg.filter(|m| !m.trim().is_empty()).unwrap_or_else(|| {
@@ -106,9 +108,10 @@ fn commit_file_update<C: Controller>(
async fn update_blob<C: Controller>(
body: Body,
controller: &C,
- mut ctx: Context,
+ ctx: Context,
+ parts: &mut Parts,
) -> Result<Response, Error> {
- if !controller.may_write_path(&ctx) {
+ if !controller.may_write_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to edit this file".into(),
));
@@ -132,40 +135,41 @@ async fn update_blob<C: Controller>(
} else {
"this file has been deleted in the meantime, if you save you will re-create it"
}
- ), controller, &ctx).into());
+ ), controller, &ctx, parts).into());
}
}
// normalize newlines as per HTML spec
let text = data.text.replace("\r\n", "\n");
- if let Err(error) = controller.before_write(&text, &mut ctx) {
- return Ok(edit_text_form(&data, Some(&error), controller, &ctx).into());
+ if let Err(error) = controller.before_write(&text, &ctx, parts) {
+ return Ok(edit_text_form(&data, Some(&error), controller, &ctx, parts).into());
}
- commit_file_update(text.as_bytes(), data.msg, controller, &ctx)?;
+ commit_file_update(text.as_bytes(), data.msg, controller, &ctx, &parts)?;
- controller.after_write(&mut ctx);
+ controller.after_write(&ctx, parts);
return Ok(Builder::new()
.status(StatusCode::FOUND)
- .header("location", ctx.parts.uri.path())
+ .header("location", parts.uri.path())
.body("redirecting".into())
- .unwrap());
+ .unwrap()
+ .into());
}
async fn upload_blob<C: Controller>(
body: Body,
controller: &C,
- mut ctx: Context,
+ ctx: Context,
+ parts: &mut Parts,
) -> Result<Response, Error> {
- if !controller.may_write_path(&ctx) {
+ if !controller.may_write_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to edit this file".into(),
));
}
// Extract the `multipart/form-data` boundary from the headers.
- let boundary = ctx
- .parts
+ let boundary = parts
.headers
.get(header::CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
@@ -180,15 +184,22 @@ async fn upload_blob<C: Controller>(
{
if field.name() == Some("file") {
// TODO: make commit message customizable
- commit_file_update(&field.bytes().await.unwrap(), None, controller, &ctx)?;
+ commit_file_update(
+ &field.bytes().await.unwrap(),
+ None,
+ controller,
+ &ctx,
+ &parts,
+ )?;
- controller.after_write(&mut ctx);
+ controller.after_write(&ctx, parts);
return Ok(Builder::new()
.status(StatusCode::FOUND)
- .header("location", ctx.parts.uri.path())
+ .header("location", parts.uri.path())
.body("redirecting".into())
- .unwrap());
+ .unwrap()
+ .into());
}
}
Err(Error::BadRequest(
@@ -200,8 +211,9 @@ async fn move_entry<C: Controller>(
body: Body,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if !controller.may_move_path(&ctx) {
+ if !controller.may_move_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to move this file".into(),
));
@@ -216,6 +228,7 @@ async fn move_entry<C: Controller>(
Some("can not move entry to itself"),
controller,
&ctx,
+ parts,
);
}
@@ -228,6 +241,7 @@ async fn move_entry<C: Controller>(
Some("destination already exists"),
controller,
&ctx,
+ parts,
);
}
@@ -252,7 +266,7 @@ async fn move_entry<C: Controller>(
let new_tree_id = builder.create_updated(&ctx.repo, &parent_commit.tree()?)?;
ctx.commit(
- &controller.signature(&ctx.repo, &ctx.parts)?,
+ &controller.signature(&ctx.repo, &parts)?,
&data
.msg
.take()
@@ -269,7 +283,8 @@ async fn move_entry<C: Controller>(
controller.build_url_path(&ctx.branch, &data.dest),
)
.body("redirecting".into())
- .unwrap())
+ .unwrap()
+ .into())
}
#[derive(Deserialize)]
@@ -281,8 +296,9 @@ async fn remove_entry<C: Controller>(
body: Body,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if !controller.may_move_path(&ctx) {
+ if !controller.may_move_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to remove this file".into(),
));
@@ -294,7 +310,7 @@ async fn remove_entry<C: Controller>(
let new_tree_id = builder.create_updated(&ctx.repo, &parent_commit.tree()?)?;
ctx.commit(
- &controller.signature(&ctx.repo, &ctx.parts)?,
+ &controller.signature(&ctx.repo, &parts)?,
&data
.msg
.filter(|m| !m.trim().is_empty())
@@ -309,15 +325,17 @@ async fn remove_entry<C: Controller>(
controller.build_url_path(&ctx.branch, ctx.path.parent().unwrap().to_str().unwrap()),
)
.body("redirecting".into())
- .unwrap())
+ .unwrap()
+ .into())
}
async fn diff_blob<C: Controller>(
body: Body,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
- if !controller.may_write_path(&ctx) {
+ if !controller.may_write_path(&ctx, parts) {
return Err(Error::Unauthorized(
"you are not authorized to edit this file".into(),
));
@@ -331,7 +349,7 @@ async fn diff_blob<C: Controller>(
let blob = ctx.repo.find_blob(entr.id()).unwrap();
let old_text = from_utf8(blob.content())?;
- let mut page = edit_text_form(&form, None, controller, &ctx);
+ let mut page = edit_text_form(&form, None, controller, &ctx, parts);
page.body.push_str(&diff(old_text, &new_text));
Ok(page.into())
}
@@ -340,10 +358,11 @@ async fn preview_edit<C: Controller>(
body: Body,
controller: &C,
ctx: Context,
+ parts: &Parts,
) -> Result<Response, Error> {
let form: EditForm = body.into_form().await?;
let new_text = form.text.replace("\r\n", "\n");
- let mut page = edit_text_form(&form, None, controller, &ctx);
+ let mut page = edit_text_form(&form, None, controller, &ctx, parts);
page.body
.push_str(&(get_renderer(&ctx.path).unwrap()(&new_text)));