diff options
Diffstat (limited to 'src/controller.rs')
-rw-r--r-- | src/controller.rs | 108 |
1 files changed, 95 insertions, 13 deletions
diff --git a/src/controller.rs b/src/controller.rs index 1f66b44..5dc2a47 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -3,10 +3,10 @@ use std::path::Path; use std::str::from_utf8; use std::sync::RwLock; -use git2::ObjectType; use git2::Repository; use git2::Signature; use git2::Tree; +use git2::{BranchType, ObjectType}; use hyper::http::request::Parts; use serde::Deserialize; use sputnik::html_escape; @@ -19,8 +19,13 @@ use crate::Page; use crate::Response; pub trait Controller { - /// Allows the controller to abort if the request is invalid. - fn before_route(&self, parts: &Parts) -> Result<(), Error>; + fn parse_url_path<'a>(&'a self, url_path: &'a str) -> Result<(Branch, &'a str), Error>; + + /// Builds a URL path from a given Git branch and file path. + fn build_url_path<'a>(&self, branch: &Branch, path: &'a str) -> String; + + /// Allows the controller to intercept the request handling. + fn before_route(&self, parts: &Parts, repo: &Repository) -> Option<Result<Response, Error>>; /// Returns some HTML info to display for the current page. fn user_info_html(&self, parts: &Parts) -> Option<String> { @@ -48,7 +53,7 @@ pub trait Controller { 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); /// Executed before writing a file. Return an error to abort the writing process. fn before_write(&self, text: &str, context: &mut Context) -> Result<(), String> { @@ -64,21 +69,29 @@ pub trait Controller { } } -pub struct SoloController; +pub struct SoloController(pub Branch); const USERNAME_HEADER: &str = "Username"; impl Controller for SoloController { - fn before_route(&self, parts: &Parts) -> Result<(), Error> { + fn parse_url_path<'a>(&'a self, url_path: &'a str) -> Result<(Branch, &'a str), Error> { + Ok((self.0.clone(), url_path)) + } + + fn build_url_path<'a>(&self, branch: &Branch, path: &'a str) -> String { + path.to_owned() + } + + fn before_route(&self, parts: &Parts, repo: &Repository) -> Option<Result<Response, Error>> { if parts.headers.contains_key(USERNAME_HEADER) { - return Err(Error::BadRequest(format!( + return Some(Err(Error::BadRequest(format!( "unexpected header {} (only \ allowed in multi-user mode), aborting to prevent accidental \ information leakage", USERNAME_HEADER - ))); + )))); } - Ok(()) + None } fn signature(&self, repo: &Repository, parts: &Parts) -> Result<Signature, Error> { @@ -97,6 +110,12 @@ impl Controller for SoloController { true } + fn before_return_tree_page(&self, page: &mut Page, tree: Option<Tree>, context: &Context) { + 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> { if let Some(ext) = ctx.path.extension().and_then(|e| e.to_str()) { validate_formats(text, ext)?; @@ -250,17 +269,80 @@ fn validate_formats(text: &str, extension: &str) -> Result<(), String> { Ok(()) } +fn multi_user_startpage( + controller: &MultiUserController, + parts: &Parts, + repo: &Repository, +) -> Result<Response, Error> { + // TODO: add domain name to title? + let mut page = Page { + title: "GitPad".into(), + controller, + parts: &parts, + body: String::new(), + header: None, + }; + + let branches: Vec<_> = repo.branches(Some(BranchType::Local))?.collect(); + + page.body.push_str("This GitPad instance has "); + + if branches.is_empty() { + page.body.push_str("no branches yet."); + } else { + page.body.push_str("the following branches:"); + page.body.push_str("<ul>"); + for (branch, _) in repo.branches(Some(BranchType::Local))?.flatten() { + page.body.push_str(&format!( + "<li><a href='~{0}/'>~{0}</a></li>", + html_escape(branch.name()?.unwrap()) + )); + } + page.body.push_str("</ul>"); + } + + Ok(page.into()) +} + impl Controller for MultiUserController { - fn before_route(&self, parts: &Parts) -> Result<(), Error> { + fn parse_url_path<'a>(&'a self, url_path: &'a str) -> Result<(Branch, &'a str), Error> { + let mut iter = url_path.splitn(3, '/'); + iter.next(); + let rev = iter.next().unwrap(); + if !rev.starts_with('~') { + return Err(Error::NotFound( + "branch name must be prefixed a tilde (~)".into(), + )); + } + let rev = &rev[1..]; + if rev.trim().is_empty() { + return Err(Error::NotFound("invalid branch name".into())); + } + let rev = Branch(rev.to_owned()); + let unsanitized_path = match iter.next() { + Some(value) => value, + None => return Err(Error::MissingTrailingSlash(url_path.to_string())), + }; + Ok((rev, unsanitized_path)) + } + + fn build_url_path<'a>(&self, branch: &Branch, path: &'a str) -> String { + format!("/~{}/{}", branch.0, path) + } + + fn before_route(&self, parts: &Parts, repo: &Repository) -> Option<Result<Response, Error>> { if !parts.headers.contains_key(USERNAME_HEADER) { - return Err(Error::BadRequest(format!( + return Some(Err(Error::BadRequest(format!( "expected header {} because of multi-user mode \ (this shouldn't be happening because a reverse-proxy \ should be used to set this header)", USERNAME_HEADER - ))); + )))); } - Ok(()) + if parts.uri.path() == "/" { + return Some(multi_user_startpage(&self, parts, repo)); + } + None } fn user_info_html(&self, parts: &Parts) -> Option<String> { |