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