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