Thumbnail and audio endpoints no longer encode payloads

This commit is contained in:
Antoine Gersant 2021-11-28 20:13:54 -08:00
parent f27bc4ccfc
commit 39c8cf7595
3 changed files with 70 additions and 9 deletions

View file

@ -2,17 +2,17 @@ use actix_files::NamedFile;
use actix_web::{ use actix_web::{
client::HttpError, client::HttpError,
delete, delete,
dev::{MessageBody, Payload, Service, ServiceRequest, ServiceResponse}, dev::{BodyEncoding, MessageBody, Payload, Service, ServiceRequest, ServiceResponse},
error::{BlockingError, ErrorForbidden, ErrorInternalServerError, ErrorUnauthorized}, error::{BlockingError, ErrorForbidden, ErrorInternalServerError, ErrorUnauthorized},
get, get,
http::StatusCode, http::{ContentEncoding, StatusCode},
post, put, post, put,
web::{self, Data, Json, JsonConfig, ServiceConfig}, web::{self, Data, Json, JsonConfig, ServiceConfig},
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, FromRequest, HttpMessage, HttpRequest, HttpResponse, Responder, ResponseError,
}; };
use actix_web_httpauth::extractors::{basic::BasicAuth, bearer::BearerAuth}; use actix_web_httpauth::extractors::{basic::BasicAuth, bearer::BearerAuth};
use cookie::{self, *}; use cookie::{self, *};
use futures_util::future::{err, ok}; use futures_util::future::{err, ok, ready, Ready};
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
use std::future::Future; use std::future::Future;
use std::ops::Deref; use std::ops::Deref;
@ -378,6 +378,34 @@ fn add_auth_cookies<T>(
Ok(()) Ok(())
} }
struct MediaFile {
named_file: NamedFile,
}
impl MediaFile {
fn new(named_file: NamedFile) -> Self {
Self {
named_file: named_file,
}
}
}
impl Responder for MediaFile {
type Error = actix_web::Error;
type Future = Ready<Result<HttpResponse, actix_web::Error>>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
let mut response = self.named_file.into_response(req);
if let Ok(r) = response.as_mut() {
// Intentionally turn off content encoding for media files because:
// 1. There is little value in compressing files that are already compressed (mp3, jpg, etc.)
// 2. The Content-Length header is incompatible with content encoding (other than identity), and can be valuable for clients
r.encoding(ContentEncoding::Identity);
}
return ready(response);
}
}
async fn block<F, I, E>(f: F) -> Result<I, APIError> async fn block<F, I, E>(f: F) -> Result<I, APIError>
where where
F: FnOnce() -> Result<I, E> + Send + 'static, F: FnOnce() -> Result<I, E> + Send + 'static,
@ -681,7 +709,7 @@ async fn get_audio(
vfs_manager: Data<vfs::Manager>, vfs_manager: Data<vfs::Manager>,
_auth: Auth, _auth: Auth,
path: web::Path<String>, path: web::Path<String>,
) -> Result<NamedFile, APIError> { ) -> Result<MediaFile, APIError> {
let audio_path = block(move || { let audio_path = block(move || {
let vfs = vfs_manager.get_vfs()?; let vfs = vfs_manager.get_vfs()?;
let path = percent_decode_str(&(path.0)).decode_utf8_lossy(); let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
@ -691,7 +719,7 @@ async fn get_audio(
.await?; .await?;
let named_file = NamedFile::open(&audio_path).map_err(|_| APIError::AudioFileIOError)?; let named_file = NamedFile::open(&audio_path).map_err(|_| APIError::AudioFileIOError)?;
Ok(named_file) Ok(MediaFile::new(named_file))
} }
#[get("/thumbnail/{path:.*}")] #[get("/thumbnail/{path:.*}")]
@ -701,7 +729,7 @@ async fn get_thumbnail(
_auth: Auth, _auth: Auth,
path: web::Path<String>, path: web::Path<String>,
options_input: web::Query<dto::ThumbnailOptions>, options_input: web::Query<dto::ThumbnailOptions>,
) -> Result<NamedFile, APIError> { ) -> Result<MediaFile, APIError> {
let options = thumbnail::Options::from(options_input.0); let options = thumbnail::Options::from(options_input.0);
let thumbnail_path = block(move || { let thumbnail_path = block(move || {
@ -719,7 +747,7 @@ async fn get_thumbnail(
let named_file = let named_file =
NamedFile::open(&thumbnail_path).map_err(|_| APIError::ThumbnailFileIOError)?; NamedFile::open(&thumbnail_path).map_err(|_| APIError::ThumbnailFileIOError)?;
Ok(named_file) Ok(MediaFile::new(named_file))
} }
#[get("/playlists")] #[get("/playlists")]

View file

@ -4,7 +4,7 @@ use crate::app::{config, ddns, settings, thumbnail, user, vfs};
use std::convert::From; use std::convert::From;
pub const API_MAJOR_VERSION: i32 = 6; pub const API_MAJOR_VERSION: i32 = 6;
pub const API_MINOR_VERSION: i32 = 0; pub const API_MINOR_VERSION: i32 = 1;
pub const COOKIE_SESSION: &str = "session"; pub const COOKIE_SESSION: &str = "session";
pub const COOKIE_USERNAME: &str = "username"; pub const COOKIE_USERNAME: &str = "username";
pub const COOKIE_ADMIN: &str = "admin"; pub const COOKIE_ADMIN: &str = "admin";

View file

@ -34,6 +34,39 @@ fn audio_golden_path() {
let response = service.fetch_bytes(&request); let response = service.fetch_bytes(&request);
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().len(), 24_142); assert_eq!(response.body().len(), 24_142);
assert_eq!(
response.headers().get(header::CONTENT_LENGTH).unwrap(),
"24142"
);
}
#[test]
fn audio_does_not_encode_content() {
let mut service = ServiceType::new(&test_name!());
service.complete_initial_setup();
service.login_admin();
service.index();
service.login();
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "02 - Candlelight.mp3"]
.iter()
.collect();
let mut request = protocol::audio(&path);
let headers = request.headers_mut();
headers.append(
header::ACCEPT_ENCODING,
HeaderValue::from_str("gzip, deflate, br").unwrap(),
);
let response = service.fetch_bytes(&request);
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().len(), 24_142);
assert_eq!(response.headers().get(header::TRANSFER_ENCODING), None);
assert_eq!(
response.headers().get(header::CONTENT_LENGTH).unwrap(),
"24142"
);
} }
#[test] #[test]