| |
|
| |
pub fn router(profile: Arc<Profile>) -> Router {
|
| |
Router::new()
|
| + |
.route("/:rid/:sha", get(commit_handler))
|
| |
.route("/:rid/:sha/*path", get(file_by_commit_handler))
|
| |
.route("/:rid/head/*path", get(file_by_canonical_head_handler))
|
| - |
.route("/:rid/archive/*refspec", get(archive_by_refspec_handler))
|
| + |
.route("/:rid/archive/*refname", get(archive_by_refname_handler))
|
| |
.route("/:rid/blobs/:oid", get(file_by_oid_handler))
|
| |
.with_state(profile)
|
| |
}
|
| |
|
| + |
async fn commit_handler(
|
| + |
Path((rid, sha)): Path<(RepoId, String)>,
|
| + |
State(profile): State<Arc<Profile>>,
|
| + |
) -> Result<(StatusCode, HeaderMap, Vec<u8>), Error> {
|
| + |
let storage = &profile.storage;
|
| + |
let repo = storage.repository(rid)?;
|
| + |
|
| + |
// Don't allow accessing private repos.
|
| + |
if repo.identity_doc()?.visibility().is_private() {
|
| + |
return Err(Error::NotFound);
|
| + |
}
|
| + |
|
| + |
if !sha.ends_with(ARCHIVE_SUFFIX) {
|
| + |
return Err(Error::NotFound);
|
| + |
}
|
| + |
|
| + |
let sha = sha.trim_end_matches(ARCHIVE_SUFFIX);
|
| + |
|
| + |
if Oid::from_str(sha).is_err() {
|
| + |
return Err(Error::NotFound);
|
| + |
}
|
| + |
|
| + |
archive_by_refname(rid, sha.to_string(), profile).await
|
| + |
}
|
| + |
|
| |
async fn file_by_commit_handler(
|
| |
Path((rid, sha, path)): Path<(RepoId, Oid, String)>,
|
| |
State(profile): State<Arc<Profile>>,
|
| |
blob_response(blob, path)
|
| |
}
|
| |
|
| - |
async fn archive_by_refspec_handler(
|
| - |
Path((rid, refspec)): Path<(RepoId, String)>,
|
| + |
async fn archive_by_refname_handler(
|
| + |
Path((rid, refname)): Path<(RepoId, String)>,
|
| |
State(profile): State<Arc<Profile>>,
|
| - |
) -> impl IntoResponse {
|
| + |
) -> Result<(StatusCode, HeaderMap, Vec<u8>), Error> {
|
| + |
archive_by_refname(rid, refname, profile).await
|
| + |
}
|
| + |
|
| + |
async fn archive_by_refname(
|
| + |
rid: RepoId,
|
| + |
refname: String,
|
| + |
profile: Arc<Profile>,
|
| + |
) -> Result<(StatusCode, HeaderMap, Vec<u8>), Error> {
|
| |
let storage = &profile.storage;
|
| |
let repo = storage.repository(rid)?;
|
| |
|
| |
let project = doc.project()?;
|
| |
let repo_name = project.name();
|
| |
|
| - |
// Remove possible .tar.gz suffix
|
| - |
let refspec = if refspec.ends_with(".tar.gz") {
|
| - |
refspec.trim_end_matches(".tar.gz")
|
| - |
} else {
|
| - |
&refspec
|
| + |
let (oid, via_refname) = match Oid::from_str(&refname) {
|
| + |
Ok(oid) => (oid, false),
|
| + |
Err(_) => (
|
| + |
repo.backend
|
| + |
.resolve_reference_from_short_name(&refname)
|
| + |
.map(|reference| reference.target())?
|
| + |
.ok_or(Error::NotFound)?
|
| + |
.into(),
|
| + |
true,
|
| + |
),
|
| |
};
|
| |
|
| - |
let oid = match Oid::from_str(&refspec) {
|
| - |
Ok(oid) => oid,
|
| - |
Err(_) => repo
|
| - |
.backend
|
| - |
.resolve_reference_from_short_name(&refspec)
|
| - |
.map(|reference| reference.target())?
|
| - |
.ok_or(Error::NotFound)?
|
| - |
.into(),
|
| - |
};
|
| - |
|
| - |
// SAFETY: Git command is available on the system, so we can safely unwrap.
|
| |
let output = Command::new("git")
|
| |
.arg("archive")
|
| |
.arg("--format=tar.gz")
|
| |
.output()?;
|
| |
|
| |
if !output.status.success() {
|
| - |
return Err(Error::Archive(format!(
|
| - |
"{}; code={}",
|
| - |
String::from_utf8_lossy(&output.stderr).trim(),
|
| - |
output.status.code().unwrap_or(0)
|
| - |
)));
|
| + |
return Err(Error::Archive(
|
| + |
output.status,
|
| + |
String::from_utf8_lossy(&output.stderr).to_string(),
|
| + |
));
|
| |
}
|
| |
|
| + |
// Build a filename for the archive, which includes the
|
| + |
// refname (if one was given):
|
| + |
//
|
| + |
// Without refname: <repo-name>-<oid>.tar.gz
|
| + |
// With refname: <repo-name>-<refname>--<oid>.tar.gz
|
| + |
let filename = {
|
| + |
let mut build = String::from(repo_name);
|
| + |
build.push('-');
|
| + |
|
| + |
if via_refname {
|
| + |
// NOTE: Sanitize refnames according to
|
| + |
// <https://git-scm.com/docs/git-check-ref-format>
|
| + |
build.push_str(&refname.replace("/", "__"));
|
| + |
build.push('-');
|
| + |
}
|
| + |
|
| + |
build.push_str(oid.to_string().as_str());
|
| + |
build.push_str(ARCHIVE_SUFFIX);
|
| + |
build
|
| + |
};
|
| + |
|
| |
let mut response_headers = HeaderMap::new();
|
| |
response_headers.insert("Content-Type", HeaderValue::from_str("application/gzip")?);
|
| |
response_headers.insert(
|
| |
"Content-Disposition",
|
| - |
HeaderValue::from_str(&format!(
|
| - |
"attachment; filename={}-{}.tar.gz",
|
| - |
repo_name,
|
| - |
refspec.replace(|c: char| !c.is_ascii(), "_")
|
| - |
))?,
|
| + |
HeaderValue::from_str(&format!("attachment; filename=\"{filename}\""))?,
|
| |
);
|
| |
Ok::<_, Error>((StatusCode::OK, response_headers, output.stdout))
|
| |
}
|