use actix_http::header::HeaderMap;
use actix_web::{web, HttpRequest, HttpResponse, HttpResponseBuilder};
use actix_web::dev::ConnectionInfo;
use actix_web::web::Data;
use log::debug;
use mime::{Mime, APPLICATION_JSON, TEXT_HTML};
use mockall_double::double;
#[double]
use crate::client::ArchiveCachingClient;
#[double]
use crate::client::TArchiveCachingClient;
use crate::config::anttp_config::AntTpConfig;
#[double]
use crate::client::ChunkCachingClient;
#[double]
use crate::client::PublicDataCachingClient;
#[double]
use crate::client::CachingClient;
#[double]
use crate::client::StreamingClient;
use crate::error::GetError;
use crate::error::chunk_error::ChunkError;
use crate::service::archive_helper::{ArchiveAction, ArchiveHelper, ArchiveInfo};
use crate::service::archive_service::ArchiveService;
#[double]
use crate::service::file_service::FileService;
use crate::service::file_service::{RangeProps};
use crate::service::header_builder::HeaderBuilder;
#[double]
use crate::service::resolver_service::ResolverService;
use crate::service::resolver_service::ResolvedAddress;
use crate::service::crypto_service::CryptoService;
#[double]
use crate::service::public_data_service::PublicDataService;
use crate::service::tarchive_service::TarchiveService;
pub async fn get_public_data(
request: HttpRequest,
path: web::Path<String>,
resolver_service: Data<ResolverService>,
caching_client_data: Data<CachingClient>,
streaming_client_data: Data<StreamingClient>,
conn: ConnectionInfo,
ant_tp_config_data: Data<AntTpConfig>,
crypto_service_data: Data<CryptoService>,
) -> Result<HttpResponse, ChunkError> {
fetch_public_data(request, path, resolver_service, caching_client_data, streaming_client_data,
conn, ant_tp_config_data, crypto_service_data, true).await
}
pub async fn head_public_data(
request: HttpRequest,
path: web::Path<String>,
resolver_service: Data<ResolverService>,
caching_client_data: Data<CachingClient>,
streaming_client_data: Data<StreamingClient>,
conn: ConnectionInfo,
ant_tp_config_data: Data<AntTpConfig>,
crypto_service_data: Data<CryptoService>,
) -> Result<HttpResponse, ChunkError> {
fetch_public_data(request, path, resolver_service, caching_client_data, streaming_client_data,
conn, ant_tp_config_data, crypto_service_data, false).await
}
async fn fetch_public_data(
request: HttpRequest,
path: web::Path<String>,
resolver_service_data: Data<ResolverService>,
caching_client_data: Data<CachingClient>,
streaming_client_data: Data<StreamingClient>,
conn: ConnectionInfo,
ant_tp_config_data: Data<AntTpConfig>,
crypto_service_data: Data<CryptoService>,
has_body: bool,
) -> Result<HttpResponse, ChunkError> {
let ant_tp_config = ant_tp_config_data.get_ref().clone();
let caching_client = caching_client_data.get_ref().clone();
let streaming_client = streaming_client_data.get_ref().clone();
let crypto_service = crypto_service_data.get_ref().clone();
let resolver_service = resolver_service_data.get_ref().clone();
match resolver_service_data.resolve(&conn.host(), &path.into_inner(), &request.headers()).await {
Some(resolved_address) => {
let header_builder = HeaderBuilder::new(resolved_address.ttl);
if !resolved_address.is_allowed {
Err(GetError::AccessNotAllowed(format!("Access forbidden: {}", hex::encode(resolved_address.xor_name))).into())
} else if !resolved_address.is_modified {
Ok(build_not_modified_response(&resolved_address, &header_builder))
} else if resolved_address.archive.is_some() {
debug!("Retrieving file from archive [{}]", hex::encode(resolved_address.xor_name));
let chunk_caching_client = ChunkCachingClient::new(caching_client.clone());
let public_data_caching_client = PublicDataCachingClient::new(caching_client.clone(), streaming_client.clone());
let file_service = FileService::new(chunk_caching_client, ant_tp_config.download_threads);
let public_data_service = PublicDataService::new(public_data_caching_client, resolver_service.clone());
let tarchive_caching_client = TArchiveCachingClient::new(caching_client.clone(), streaming_client.clone());
let tarchive_service = TarchiveService::new(public_data_service, tarchive_caching_client, file_service.clone(), resolver_service.clone(), ant_tp_config);
let archive_caching_client = ArchiveCachingClient::new(caching_client, streaming_client);
let archive_service = ArchiveService::new(tarchive_service, resolver_service.clone(), archive_caching_client, file_service.clone());
let archive_info = archive_service.get_archive_info(&resolved_address, &request).await;
match archive_info.action {
ArchiveAction::Data => {
let signature_verified = verify_signature(&request, &resolved_address, &crypto_service);
get_data_archive(&request, &resolved_address, &header_builder, file_service, archive_info, signature_verified, has_body).await
},
ArchiveAction::Redirect => Ok(build_moved_permanently_response(&request.path(), &header_builder)),
ArchiveAction::Listing => Ok(build_list_files_response(&request, &resolved_address, &header_builder, has_body)),
ArchiveAction::NotFound => Err(GetError::RecordNotFound(format!("File not found: {}", request.full_url())).into()),
}
} else {
debug!("Retrieving file from XOR [{}]", hex::encode(resolved_address.xor_name));
let chunk_caching_client = ChunkCachingClient::new(caching_client.clone());
let file_service = FileService::new(chunk_caching_client, ant_tp_config.download_threads);
let signature_verified = verify_signature(&request, &resolved_address, &crypto_service);
get_data_xor(&request, &resolved_address, &header_builder, file_service, signature_verified, has_body).await
}
},
None => Err(GetError::RecordNotFound(format!("File not found: {}", request.full_url())).into())
}
}
fn verify_signature(request: &HttpRequest, resolved_address: &ResolvedAddress, crypto_service: &CryptoService) -> Option<bool> {
let signature_hex = if let Some(data_signature_header) = request.headers().get("x-data-signature") {
data_signature_header.to_str().ok().map(|s| s.to_string())
} else if let Some(archive) = &resolved_address.archive {
archive.map().get(&resolved_address.file_path).and_then(|data_address_offset| data_address_offset.signature.clone())
} else {
None
};
if let (Some(signer_public_key_header), Some(signature_hex)) = (request.headers().get("x-signer-public-key"), signature_hex) {
if let Ok(signer_public_key_hex) = signer_public_key_header.to_str() {
return Some(crypto_service.verify(
signer_public_key_hex,
&signature_hex,
hex::encode(resolved_address.xor_name).as_str()
));
}
}
None
}
fn build_not_modified_response(resolved_address: &ResolvedAddress, header_builder: &HeaderBuilder) -> HttpResponse {
HttpResponse::NotModified()
.insert_header(header_builder.build_cache_control_header(resolved_address.is_resolved_from_mutable))
.insert_header(header_builder.build_expires_header(resolved_address.is_resolved_from_mutable))
.insert_header(header_builder.build_etag_header(&resolved_address.xor_name))
.insert_header(header_builder.build_cors_header())
.insert_header(header_builder.build_server_header())
.finish()
}
fn build_moved_permanently_response(request_path: &str, header_builder: &HeaderBuilder) -> HttpResponse {
HttpResponse::MovedPermanently()
.insert_header(header_builder.build_location_header(format!("{}/", request_path)))
.insert_header(header_builder.build_server_header())
.finish()
}
fn build_list_files_response(request: &HttpRequest, resolved_address: &ResolvedAddress, header_builder: &HeaderBuilder, has_body: bool) -> HttpResponse {
let archive_helper = ArchiveHelper::new(resolved_address.archive.clone().unwrap());
let mime = get_accept_header_value(request.headers());
let body = if has_body {
archive_helper.list_files(resolved_address.file_path.clone(), request.headers())
} else {
"".to_string()
};
if mime == APPLICATION_JSON {
HttpResponse::Ok()
.insert_header(header_builder.build_etag_header(&resolved_address.xor_name))
.insert_header(header_builder.build_cors_header())
.insert_header(header_builder.build_server_header())
.insert_header(header_builder.build_content_type_header_from_mime(&mime))
.body(body)
} else {
HttpResponse::Ok()
.insert_header(header_builder.build_cors_header())
.insert_header(header_builder.build_server_header())
.insert_header(header_builder.build_content_type_header_from_mime(&mime))
.body(body)
}
}
fn update_partial_content_response(builder: &mut HttpResponseBuilder, resolved_address: &ResolvedAddress, header_builder: &HeaderBuilder, range_props: &RangeProps, modified_time: Option<u64>, signature_verified: Option<bool>) {
builder
.insert_header(header_builder.build_content_range_header(range_props.range_from().unwrap(), range_props.range_to().unwrap(), range_props.content_length()))
.insert_header(header_builder.build_accept_ranges_header())
.insert_header(header_builder.build_cache_control_header(resolved_address.is_resolved_from_mutable))
.insert_header(header_builder.build_expires_header(resolved_address.is_resolved_from_mutable))
.insert_header(header_builder.build_etag_header(&resolved_address.xor_name))
.insert_header(header_builder.build_cors_header())
.insert_header(header_builder.build_server_header())
.insert_header(header_builder.build_content_type_header(range_props.extension()));
if let Some(modified_time) = modified_time {
builder.insert_header(header_builder.build_last_modified_header(modified_time));
}
if let Some(verified) = signature_verified {
builder.insert_header(("x-data-signature-verified", verified.to_string()));
}
if let Some(archive) = &resolved_address.archive {
if let Some(data_address_offset) = archive.map().get(&resolved_address.file_path) {
if let Some(signature) = &data_address_offset.signature {
builder.insert_header(("x-data-signature", signature.clone()));
}
}
}
}
fn update_full_content_response(builder: &mut HttpResponseBuilder, resolved_address: &ResolvedAddress, header_builder: &HeaderBuilder, range_props: &RangeProps, modified_time: Option<u64>, signature_verified: Option<bool>) {
builder
.insert_header(header_builder.build_content_length_header(range_props.content_length()))
.insert_header(header_builder.build_cache_control_header(resolved_address.is_resolved_from_mutable))
.insert_header(header_builder.build_expires_header(resolved_address.is_resolved_from_mutable))
.insert_header(header_builder.build_etag_header(&resolved_address.xor_name))
.insert_header(header_builder.build_cors_header())
.insert_header(header_builder.build_server_header())
.insert_header(header_builder.build_content_type_header(range_props.extension()));
if let Some(modified_time) = modified_time {
builder.insert_header(header_builder.build_last_modified_header(modified_time));
}
if let Some(verified) = signature_verified {
builder.insert_header(("x-data-signature-verified", verified.to_string()));
}
if let Some(archive) = &resolved_address.archive {
if let Some(data_address_offset) = archive.map().get(&resolved_address.file_path) {
if let Some(signature) = &data_address_offset.signature {
builder.insert_header(("x-data-signature", signature.clone()));
}
}
}
}
async fn get_data_archive(request: &HttpRequest, resolved_address: &ResolvedAddress, header_builder: &HeaderBuilder, file_service: FileService, archive_info: ArchiveInfo, signature_verified: Option<bool>, has_body: bool) -> Result<HttpResponse, ChunkError> {
let (chunk_receiver, range_props) = file_service.download_data_request(request, archive_info.path_string, archive_info.resolved_xor_addr, archive_info.offset, archive_info.size).await?;
if range_props.is_range() {
let mut builder = HttpResponse::PartialContent();
update_partial_content_response(&mut builder, &resolved_address, &header_builder, &range_props, Some(archive_info.modified_time), signature_verified);
if has_body {
Ok(builder.streaming(chunk_receiver))
} else {
Ok(builder.no_chunking(range_props.content_length()).streaming(chunk_receiver))
}
} else {
let mut builder = HttpResponse::Ok();
update_full_content_response(&mut builder, &resolved_address, &header_builder, &range_props, Some(archive_info.modified_time), signature_verified);
if has_body {
Ok(builder.streaming(chunk_receiver))
} else {
Ok(builder.no_chunking(range_props.content_length()).streaming(chunk_receiver))
}
}
}
fn get_accept_header_value(header_map: &HeaderMap) -> Mime {
if header_map.contains_key("Accept")
&& header_map.get("Accept").unwrap().to_str().unwrap_or("").to_string().contains( "json") {
APPLICATION_JSON
} else {
TEXT_HTML
}
}
async fn get_data_xor(request: &HttpRequest, resolved_address: &ResolvedAddress, header_builder: &HeaderBuilder, file_service: FileService, signature_verified: Option<bool>, has_body: bool) -> Result<HttpResponse, ChunkError> {
let (chunk_receiver, range_props) = file_service.get_data(&request, &resolved_address).await?;
if range_props.is_range() {
let mut builder = HttpResponse::PartialContent();
update_partial_content_response(&mut builder, &resolved_address, &header_builder, &range_props, None, signature_verified);
if has_body {
Ok(builder.streaming(chunk_receiver))
} else {
Ok(builder.no_chunking(range_props.content_length()).streaming(chunk_receiver))
}
} else {
let mut builder = HttpResponse::Ok();
update_full_content_response(&mut builder, &resolved_address, &header_builder, &range_props, None, signature_verified);
if has_body {
Ok(builder.streaming(chunk_receiver))
} else {
Ok(builder.no_chunking(range_props.content_length()).streaming(chunk_receiver))
}
}
}