raw
1use axum::{
2 extract::Path,
3 http::{HeaderValue, StatusCode, Uri, header},
4 response::{IntoResponse as _, Response},
5};
6
7use crate::utils::{
8 Error, Result, error::Context as _, extractor::repo_name_checks, git::Repository,
9 spawn_blocking,
10};
11
12#[tracing::instrument(skip_all)]
13pub async fn get_1(uri: Uri, Path(repo_name): Path<String>) -> Response {
14 spawn_blocking(move || inner(&uri, &repo_name).into_response()).await
15}
16
17#[tracing::instrument(skip_all)]
18pub async fn get_2(uri: Uri, Path((repo_name, _)): Path<(String, String)>) -> Response {
19 spawn_blocking(move || inner(&uri, &repo_name).into_response()).await
20}
21
22fn inner(uri: &Uri, repo_name: &str) -> Result {
23 repo_name_checks(repo_name)?;
24
25 let Some(repo) = Repository::open(repo_name).context("opening repository")? else {
26 return Err(Error::new(StatusCode::NOT_FOUND, "repo does not exist"));
27 };
28
29 let path = uri
30 .path()
31 .strip_prefix(&format!("/{repo_name}/"))
32 .unwrap_or_default();
33
34 let path = repo.path().join(path);
35
36 if !path.exists() {
38 return Err(Error::new(
39 StatusCode::NOT_FOUND,
40 "This page does not exist.",
41 ));
42 }
43
44 let path = path.canonicalize().context("canonicalize new path")?;
45
46 if !path.starts_with(repo.path()) {
48 tracing::warn!("Attempt to acces file outside of repo dir: {:?}", path);
49 return Err(Error::new(
50 StatusCode::FORBIDDEN,
51 "You do not have access to this file.",
52 ));
53 }
54
55 if !path.is_file() {
58 return Err(Error::new(
59 StatusCode::NOT_FOUND,
60 "This page does not exist.",
61 ));
62 }
63
64 let body = std::fs::read(&path).context("reading file")?;
65
66 Ok((
67 StatusCode::OK,
68 [(
69 header::CONTENT_TYPE,
70 HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
71 )],
72 body,
73 )
74 .into_response())
75}
76