diff --git a/src/service/actix/api.rs b/src/service/actix/api.rs index 21de854..950eca7 100644 --- a/src/service/actix/api.rs +++ b/src/service/actix/api.rs @@ -2,17 +2,17 @@ use actix_files::NamedFile; use actix_web::{ client::HttpError, delete, - dev::{MessageBody, Payload, Service, ServiceRequest, ServiceResponse}, + dev::{BodyEncoding, MessageBody, Payload, Service, ServiceRequest, ServiceResponse}, error::{BlockingError, ErrorForbidden, ErrorInternalServerError, ErrorUnauthorized}, get, - http::StatusCode, + http::{ContentEncoding, StatusCode}, post, put, 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 cookie::{self, *}; -use futures_util::future::{err, ok}; +use futures_util::future::{err, ok, ready, Ready}; use percent_encoding::percent_decode_str; use std::future::Future; use std::ops::Deref; @@ -378,6 +378,34 @@ fn add_auth_cookies( 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>; + + 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: F) -> Result where F: FnOnce() -> Result + Send + 'static, @@ -681,7 +709,7 @@ async fn get_audio( vfs_manager: Data, _auth: Auth, path: web::Path, -) -> Result { +) -> Result { let audio_path = block(move || { let vfs = vfs_manager.get_vfs()?; let path = percent_decode_str(&(path.0)).decode_utf8_lossy(); @@ -691,7 +719,7 @@ async fn get_audio( .await?; let named_file = NamedFile::open(&audio_path).map_err(|_| APIError::AudioFileIOError)?; - Ok(named_file) + Ok(MediaFile::new(named_file)) } #[get("/thumbnail/{path:.*}")] @@ -701,7 +729,7 @@ async fn get_thumbnail( _auth: Auth, path: web::Path, options_input: web::Query, -) -> Result { +) -> Result { let options = thumbnail::Options::from(options_input.0); let thumbnail_path = block(move || { @@ -719,7 +747,7 @@ async fn get_thumbnail( let named_file = NamedFile::open(&thumbnail_path).map_err(|_| APIError::ThumbnailFileIOError)?; - Ok(named_file) + Ok(MediaFile::new(named_file)) } #[get("/playlists")] diff --git a/src/service/dto.rs b/src/service/dto.rs index d7d7527..06d6350 100644 --- a/src/service/dto.rs +++ b/src/service/dto.rs @@ -4,7 +4,7 @@ use crate::app::{config, ddns, settings, thumbnail, user, vfs}; use std::convert::From; 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_USERNAME: &str = "username"; pub const COOKIE_ADMIN: &str = "admin"; diff --git a/src/service/test/media.rs b/src/service/test/media.rs index 9c171b9..86e5833 100644 --- a/src/service/test/media.rs +++ b/src/service/test/media.rs @@ -34,6 +34,39 @@ fn audio_golden_path() { 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::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]