raw
1use axum::{
2 extract::State,
3 http::{HeaderValue, StatusCode, Uri, header},
4 response::{IntoResponse as _, Response},
5};
6
7use crate::{
8 BileState,
9 error::Context as _,
10 git::Repository,
11 http::{
12 extractor::RepoName,
13 path::Path,
14 response::{ErrorPage, Result},
15 },
16};
17
18#[tracing::instrument(skip_all)]
19pub(crate) async fn get_1(
20 state: State<BileState>,
21 uri: Uri,
22 Path(repo_name): Path<RepoName>,
23) -> Response {
24 state
25 .spawn(move |state| inner(&state, &uri, &repo_name))
26 .await
27}
28
29#[tracing::instrument(skip_all)]
30pub(crate) async fn get_2(
31 state: State<BileState>,
32 uri: Uri,
33 Path((repo_name, _)): Path<(RepoName, String)>,
34) -> Response {
35 state
36 .spawn(move |state| inner(&state, &uri, &repo_name))
37 .await
38}
39
40fn inner(state: &BileState, uri: &Uri, repo_name: &RepoName) -> Result<Response> {
41 let Some(repo) = Repository::open(&state.config, repo_name).context("opening repository")?
42 else {
43 return Ok(ErrorPage::from(state)
44 .with_status(StatusCode::NOT_FOUND)
45 .into_response());
46 };
47
48 let path = uri
49 .path()
50 .strip_prefix(&format!("/{repo_name}/"))
51 .unwrap_or_default();
52
53 let path = repo.path().join(path);
54
55 if !path.exists() {
57 return Ok(ErrorPage::from(state)
58 .with_status(StatusCode::NOT_FOUND)
59 .into_response());
60 }
61
62 let path = path.canonicalize().context("canonicalize new path")?;
63
64 if !path.starts_with(repo.path()) {
66 tracing::warn!("Attempt to acces file outside of repo dir: {:?}", path);
67 return Ok(ErrorPage::from(state)
68 .with_status(StatusCode::FORBIDDEN)
69 .into_response());
70 }
71
72 if !path.is_file() {
75 return Ok(ErrorPage::from(state)
76 .with_status(StatusCode::NOT_FOUND)
77 .into_response());
78 }
79
80 let body = std::fs::read(&path).context("reading file")?;
81
82 Ok((
83 StatusCode::OK,
84 [(
85 header::CONTENT_TYPE,
86 HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
87 )],
88 body,
89 )
90 .into_response())
91}
92