diff options
author | Martin Fischer <martin@push-f.com> | 2021-06-24 19:04:23 +0200 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-06-24 19:41:05 +0200 |
commit | 26298bcd7ef204db4396ca2d0e603fc183220cd2 (patch) | |
tree | 9478019c3a365cb71b7cc41e12885ad25d7592b5 | |
parent | d49d835e63ec654e3a5bf75b3b365354460382e8 (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.
-rw-r--r-- | src/controller.rs | 78 | ||||
-rw-r--r-- | src/forms.rs | 22 | ||||
-rw-r--r-- | src/get_routes.rs | 77 | ||||
-rw-r--r-- | src/main.rs | 124 | ||||
-rw-r--r-- | src/post_routes.rs | 91 |
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(¶ms.action, controller, &ctx)), - controller, - parts: &ctx.parts, + header: Some(action_links(¶ms.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(¶ms.action, controller, &ctx)), - controller, - parts: &ctx.parts, + header: Some(action_links(¶ms.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, ¶ms.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(¶ms.action, controller, &ctx)), + header: Some(action_links(¶ms.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, ¶ms, controller, ctx, body).await; + if parts.method == Method::POST { + return post_routes::build_response(&args, ¶ms, 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))); |