aboutsummaryrefslogtreecommitdiff
path: root/src/get_routes.rs
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-06-23 14:12:02 +0200
committerMartin Fischer <martin@push-f.com>2021-06-23 14:36:17 +0200
commit3ed32ef268b54965c97b13efdf24a1539c4ec1c1 (patch)
treea0f80b097339693763a0bce2896c083c1000c6cc /src/get_routes.rs
parent086e0e30e47f796a12809036dfc787490904a8ae (diff)
refactor: split up main.rs into multiple modules
Diffstat (limited to 'src/get_routes.rs')
-rw-r--r--src/get_routes.rs361
1 files changed, 361 insertions, 0 deletions
diff --git a/src/get_routes.rs b/src/get_routes.rs
new file mode 100644
index 0000000..433e676
--- /dev/null
+++ b/src/get_routes.rs
@@ -0,0 +1,361 @@
+use chrono::NaiveDateTime;
+use git2::FileMode;
+use git2::ObjectType;
+use git2::Oid;
+use git2::Tree;
+use git2::TreeEntry;
+use hyper::header;
+use hyper::http::response::Builder;
+use hyper::StatusCode;
+use serde::Deserialize;
+use sputnik::html_escape;
+use sputnik::request::SputnikParts;
+use std::str::from_utf8;
+
+use crate::action_links;
+use crate::controller::Controller;
+use crate::diff::diff;
+use crate::forms;
+use crate::get_renderer;
+use crate::ActionParam;
+use crate::Context;
+use crate::Error;
+use crate::Page;
+use crate::Response;
+
+#[derive(Deserialize)]
+struct LogParam {
+ commit: Option<String>,
+}
+
+pub(crate) fn view_blob<C: Controller>(
+ entr: TreeEntry,
+ params: ActionParam,
+ controller: &C,
+ ctx: Context,
+) -> Result<Response, Error> {
+ let filename = ctx.path.file_name().unwrap().to_str().unwrap();
+
+ match params.action.as_ref() {
+ "view" => {
+ let mut page = Page {
+ title: filename.to_string(),
+ body: String::new(),
+ header: Some(action_links(&params.action, controller, &ctx)),
+ controller,
+ parts: &ctx.parts,
+ };
+
+ if let Some(access_info_html) = controller.access_info_html(&ctx) {
+ page.body.push_str(&access_info_html);
+ }
+
+ if entr.filemode() == FileMode::Link.into() {
+ // TODO: indicate and link symbolic link
+ }
+
+ let blob = ctx.repo.find_blob(entr.id()).unwrap();
+
+ if let Some(mime) = mime_guess::from_path(&ctx.path).first() {
+ if mime.type_() == "image" {
+ page.body
+ .push_str("<div class=img-container><img src=?action=raw></div>");
+ return Ok(page.into());
+ }
+ }
+
+ match from_utf8(blob.content()) {
+ Ok(text) => {
+ if let Some(renderer) = get_renderer(&ctx.path) {
+ page.body.push_str(&renderer(text));
+ } else {
+ page.body
+ .push_str(&format!("<pre>{}</pre>", html_escape(text)));
+ }
+ }
+ Err(_) => page.body.push_str("failed to decode file as UTF-8"),
+ }
+
+ Ok(page.into())
+ }
+ "edit" => {
+ if !controller.may_write_path(&ctx) {
+ return Err(Error::Unauthorized(
+ "you are not authorized to edit this file".into(),
+ ctx,
+ ));
+ }
+ let blob = ctx.repo.find_blob(entr.id()).unwrap();
+ if let Ok(text) = from_utf8(blob.content()) {
+ return Ok(forms::edit_text_form(
+ &forms::EditForm {
+ text: text.to_string(),
+ oid: Some(entr.id().to_string()),
+ ..Default::default()
+ },
+ None,
+ controller,
+ &ctx,
+ )
+ .into());
+ } else {
+ return Ok(forms::upload_form(true, controller, &ctx).into());
+ }
+ }
+ "upload" => {
+ return Ok(forms::upload_form(true, controller, &ctx).into());
+ }
+ "log" => {
+ let log_param: LogParam = ctx.parts.query().unwrap();
+
+ if let Some(commit) = log_param.commit {
+ let branch_commit = ctx.branch_head()?;
+
+ let commit = ctx
+ .repo
+ .find_commit(Oid::from_str(&commit)?)
+ .map_err(|_| Error::NotFound("commit not found".into()))?;
+
+ if branch_commit.id() != commit.id()
+ && !ctx
+ .repo
+ .graph_descendant_of(branch_commit.id(), commit.id())?
+ {
+ // disallow viewing commits from other branches you shouldn't have access to
+ return Err(Error::NotFound("commit not found".into()));
+ }
+
+ let blob_id = if let Ok(entry) = commit.tree()?.get_path(&ctx.path) {
+ entry.id()
+ } else {
+ return Ok(Page {
+ title: format!("Commit for {}", filename),
+ body: "file removed".into(),
+ header: Some(action_links(&params.action, controller, &ctx)),
+ controller,
+ parts: &ctx.parts,
+ }
+ .into());
+ };
+
+ // TODO: if UTF-8 decoding fails, link ?action=raw&rev=
+ // TODO: what if there are multiple parents?
+ let old_blob_id = commit
+ .parents()
+ .next()
+ .and_then(|p| p.tree().unwrap().get_path(&ctx.path).ok())
+ .map(|e| e.id());
+ if Some(blob_id) != old_blob_id {
+ let mut page = Page {
+ title: format!("Commit for {}", filename),
+ header: Some(action_links(&params.action, controller, &ctx)),
+ body: format!(
+ "<h1>{}</h1>{} committed on {}",
+ html_escape(commit.summary().unwrap_or_default()),
+ html_escape(commit.author().name().unwrap_or_default()),
+ NaiveDateTime::from_timestamp(commit.time().seconds(), 0)
+ .format("%b %d, %Y, %H:%M")
+ ),
+ controller,
+ parts: &ctx.parts,
+ };
+ if let Some(old_blob_id) = old_blob_id {
+ page.body.push_str(&diff(
+ from_utf8(ctx.repo.find_blob(old_blob_id)?.content())?,
+ from_utf8(ctx.repo.find_blob(blob_id)?.content())?,
+ ))
+ } else {
+ page.body.push_str(&diff(
+ "",
+ from_utf8(&ctx.repo.find_blob(blob_id)?.content())?,
+ ));
+ }
+ return Ok(page.into());
+ } else {
+ return Err(Error::NotFound("commit not found".into()));
+ }
+ }
+
+ let mut page = Page {
+ title: format!("Log for {}", filename),
+ body: String::new(),
+ header: Some(action_links(&params.action, controller, &ctx)),
+ controller,
+ parts: &ctx.parts,
+ };
+
+ let mut walk = ctx.repo.revwalk()?;
+ let branch_head = ctx.branch_head()?;
+ 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 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) {
+ let blobid = entr.id();
+ if Some(blobid) != prev_blobid {
+ commits.push(prev_commit);
+ prev_blobid = Some(blobid);
+ }
+ prev_commit = commit;
+ } else {
+ if prev_blobid.is_some() {
+ commits.push(prev_commit);
+ }
+ prev_commit = commit;
+ prev_blobid = None;
+ }
+ }
+ if prev_commit.parent_count() == 0 && prev_commit.tree()?.get_path(&ctx.path).is_ok() {
+ // the very first commit of the branch
+ commits.push(prev_commit);
+ }
+ let mut prev_date = None;
+ for c in commits {
+ let date = NaiveDateTime::from_timestamp(c.time().seconds(), 0).date();
+ if Some(date) != prev_date {
+ if prev_date != None {
+ page.body.push_str("</ul>");
+ }
+ page.body
+ .push_str(&format!("{}<ul>", date.format("%b %d, %Y")));
+ }
+
+ page.body.push_str(&format!(
+ "<li><a href='?action=log&commit={}'>{}: {}</a></li>",
+ html_escape(c.id().to_string()),
+ html_escape(c.author().name().unwrap_or_default()),
+ html_escape(c.summary().unwrap_or_default()),
+ ));
+ prev_date = Some(date);
+ }
+ page.body.push_str("</ul>");
+ Ok(page.into())
+ }
+ "raw" => {
+ if let Some(etag) = ctx
+ .parts
+ .headers
+ .get(header::IF_NONE_MATCH)
+ .and_then(|v| v.to_str().ok())
+ {
+ if etag.trim_matches('"') == entr.id().to_string() {
+ return Ok(Builder::new()
+ .status(StatusCode::NOT_MODIFIED)
+ .body("".into())
+ .unwrap());
+ }
+ }
+
+ let blob = ctx.repo.find_blob(entr.id()).unwrap();
+ let mut resp = Response::new(blob.content().to_owned().into());
+
+ resp.headers_mut()
+ .insert(header::ETAG, format!("\"{}\"", entr.id()).parse().unwrap());
+ resp.headers_mut()
+ .insert(header::CACHE_CONTROL, "no-cache".parse().unwrap());
+
+ if let Some(mime) = mime_guess::from_path(&ctx.path).first() {
+ if mime.type_() == "text" {
+ // workaround for Firefox, which downloads non-plain text subtypes
+ // instead of displaying them (https://bugzilla.mozilla.org/1319262)
+ resp.headers_mut()
+ .insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
+ } else {
+ resp.headers_mut()
+ .insert(header::CONTENT_TYPE, mime.to_string().parse().unwrap());
+ }
+ }
+ Ok(resp)
+ }
+ "move" => {
+ if !controller.may_move_path(&ctx) {
+ return Err(Error::Unauthorized(
+ "you are not authorized to move this file".into(),
+ ctx,
+ ));
+ }
+ return forms::move_form(
+ filename,
+ &forms::MoveForm {
+ dest: ctx.path.to_str().unwrap().to_owned(),
+ msg: None,
+ },
+ None,
+ controller,
+ &ctx,
+ );
+ }
+ "remove" => {
+ if !controller.may_move_path(&ctx) {
+ return Err(Error::Unauthorized(
+ "you are not authorized to remove this file".into(),
+ ctx,
+ ));
+ }
+ let page = Page {
+ title: format!("Remove {}", filename),
+ controller,
+ parts: &ctx.parts,
+ header: Some(action_links(&params.action, controller, &ctx)),
+ body: "<form method=post autocomplete=off>\
+ <label>Message <input name=msg autofocus></label>\
+ <button>Remove</button></form>"
+ .into(),
+ };
+
+ Ok(page.into())
+ }
+ _ => Err(Error::BadRequest("unknown action".into())),
+ }
+}
+
+pub fn view_tree<C: Controller>(
+ tree: Result<Tree, git2::Error>,
+ controller: &C,
+ ctx: &Context,
+) -> 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>");
+ page.body
+ .push_str("<li><a href='..' title='go to parent directory'>../</a></li>");
+
+ if let Ok(tree) = &tree {
+ let mut entries: Vec<_> = tree.iter().collect();
+ entries.sort_by_key(|a| a.kind().unwrap().raw());
+
+ for entry in entries {
+ // if the name isn't valid utf8 we skip the entry
+ if let Some(name) = entry.name() {
+ if entry.kind() == Some(ObjectType::Tree) {
+ page.body.push_str(&format!(
+ "<li><a href='{0}/'>{0}/</a></li>",
+ html_escape(name)
+ ));
+ } else {
+ page.body.push_str(&format!(
+ "<li><a href='{0}'>{0}</a></li>",
+ html_escape(name)
+ ));
+ }
+ }
+ }
+ }
+ page.body.push_str("</ul>");
+
+ controller.before_return_tree_page(&mut page, tree.ok(), ctx);
+
+ Ok(page.into())
+}