HTTP: - Return Err(...) instead of Ok(HttpResponse::...) for error cases so actix returns correct HTTP status codes instead of 200 - Add 30s timeout on info_refs and handle_git_rpc git subprocess calls - Add 1MB pre-PACK limit to prevent memory exhaustion on receive-pack - Enforce branch protection rules (forbid push/force-push/deletion/tag) - Simplify graceful shutdown (remove manual signal handling) SSH: - Fix build_git_command: use block match arms so chained .arg() calls are on the Command, not the match expression's () result - Add MAX_RETRIES=5 to forward() data-pump loop to prevent infinite spin on persistent network failures - Fall back to raw path if canonicalize() fails instead of panicking - Add platform-specific git config paths (/dev/null on unix, NUL on win) - Start rate limiter cleanup background task so HashMap doesn't grow unbounded over time - Derive Clone on RateLimiter so SshRateLimiter::start_cleanup works
108 lines
3.5 KiB
Rust
108 lines
3.5 KiB
Rust
use crate::http::HttpAppState;
|
|
use crate::http::auth::authorize_repo_access;
|
|
use crate::http::handler::GitHttpHandler;
|
|
use crate::http::utils::get_repo_model;
|
|
use crate::ssh::RepoReceiveSyncTask;
|
|
use actix_web::{Error, HttpRequest, HttpResponse, web};
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
use tokio::time::timeout;
|
|
|
|
pub async fn info_refs(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let ip = extract_ip(&req);
|
|
if !state.rate_limiter.is_ip_read_allowed(&ip).await {
|
|
return Err(actix_web::error::ErrorTooManyRequests(
|
|
"Rate limit exceeded",
|
|
));
|
|
}
|
|
|
|
let service_param = req
|
|
.query_string()
|
|
.split('&')
|
|
.find(|s| s.starts_with("service="))
|
|
.and_then(|s| s.strip_prefix("service="))
|
|
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing service parameter"))?;
|
|
|
|
if service_param != "git-upload-pack" && service_param != "git-receive-pack" {
|
|
return Err(actix_web::error::ErrorBadRequest("Invalid service"));
|
|
}
|
|
|
|
let path_inner = path.into_inner();
|
|
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
|
|
let is_write = service_param == "git-receive-pack";
|
|
authorize_repo_access(&req, &state.db, &model, is_write).await?;
|
|
|
|
let storage_path = PathBuf::from(&model.storage_path);
|
|
let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
|
|
handler.info_refs(service_param).await
|
|
}
|
|
|
|
pub async fn upload_pack(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
payload: web::Payload,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let ip = extract_ip(&req);
|
|
if !state.rate_limiter.is_ip_read_allowed(&ip).await {
|
|
return Err(actix_web::error::ErrorTooManyRequests(
|
|
"Rate limit exceeded",
|
|
));
|
|
}
|
|
|
|
let path_inner = path.into_inner();
|
|
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
|
|
authorize_repo_access(&req, &state.db, &model, false).await?;
|
|
|
|
let storage_path = PathBuf::from(&model.storage_path);
|
|
let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
|
|
handler.upload_pack(payload).await
|
|
}
|
|
|
|
pub async fn receive_pack(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
payload: web::Payload,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let ip = extract_ip(&req);
|
|
if !state.rate_limiter.is_ip_write_allowed(&ip).await {
|
|
return Err(actix_web::error::ErrorTooManyRequests(
|
|
"Rate limit exceeded",
|
|
));
|
|
}
|
|
|
|
let path_inner = path.into_inner();
|
|
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
|
|
authorize_repo_access(&req, &state.db, &model, true).await?;
|
|
|
|
let storage_path = PathBuf::from(&model.storage_path);
|
|
let handler = GitHttpHandler::new(storage_path, model.clone(), state.db.clone());
|
|
let result = handler.receive_pack(payload).await;
|
|
|
|
let _ = tokio::spawn({
|
|
let sync = state.sync.clone();
|
|
let repo_uid = model.id;
|
|
async move {
|
|
let _ = timeout(
|
|
Duration::from_secs(5),
|
|
sync.send(RepoReceiveSyncTask { repo_uid }),
|
|
)
|
|
.await;
|
|
}
|
|
});
|
|
|
|
result
|
|
}
|
|
|
|
fn extract_ip(req: &HttpRequest) -> String {
|
|
req.connection_info()
|
|
.realip_remote_addr()
|
|
.unwrap_or("unknown")
|
|
.to_string()
|
|
}
|