diff --git a/Cargo.lock b/Cargo.lock index d9de10a7..bf4672cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1317,6 +1317,7 @@ dependencies = [ "librqbit-upnp", "lru", "memmap2", + "mime_guess", "openssl", "parking_lot", "rand", @@ -1539,6 +1540,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2822,6 +2833,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/crates/librqbit/Cargo.toml b/crates/librqbit/Cargo.toml index e5fcf9e2..7fce752a 100644 --- a/crates/librqbit/Cargo.toml +++ b/crates/librqbit/Cargo.toml @@ -74,6 +74,7 @@ rlimit = "0.10.1" async-stream = "0.3.5" memmap2 = { version = "0.9.4" } lru = { version = "0.12.3", optional = true } +mime_guess = { version = "2.0.5", default-features = false} [dev-dependencies] futures = { version = "0.3" } diff --git a/crates/librqbit/src/api.rs b/crates/librqbit/src/api.rs index 2509963b..78ed72b7 100644 --- a/crates/librqbit/src/api.rs +++ b/crates/librqbit/src/api.rs @@ -78,6 +78,12 @@ impl Api { make_torrent_details(&info_hash, &handle.info().info, only_files.as_deref()) } + pub fn torrent_file_mime_type(&self, idx: TorrentId, file_idx: usize) -> Result<&'static str> { + let handle = self.mgr_handle(idx)?; + let info = &handle.info().info; + torrent_file_mime_type(info, file_idx) + } + pub fn api_peer_stats( &self, idx: TorrentId, @@ -320,3 +326,23 @@ fn make_torrent_details( files, }) } + +fn torrent_file_mime_type( + info: &TorrentMetaV1Info, + file_idx: usize, +) -> Result<&'static str> { + info.iter_filenames_and_lengths()? + .nth(file_idx) + .and_then(|(f, _)| { + f.iter_components() + .last() + .and_then(|r| r.ok()) + .and_then(|s| mime_guess::from_path(s).first_raw()) + }) + .ok_or_else(|| { + ApiError::new_from_text( + StatusCode::INTERNAL_SERVER_ERROR, + "cannot determine mime type for file", + ) + }) +} diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index 280c7d69..753e3ce3 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -167,6 +167,13 @@ impl HttpApi { let mut output_headers = HeaderMap::new(); output_headers.insert("Accept-Ranges", HeaderValue::from_static("bytes")); + if let Ok(mime) = state.torrent_file_mime_type(idx, file_id) { + output_headers.insert( + http::header::CONTENT_TYPE, + HeaderValue::from_str(mime).context("bug - invalid MIME")?, + ); + } + let range_header = headers.get(http::header::RANGE); trace!(torrent_id=idx, file_id=file_id, range=?range_header, "request for HTTP stream"); @@ -199,12 +206,12 @@ impl HttpApi { )) .context("bug")?, ); - } else { - output_headers.insert( - http::header::CONTENT_LENGTH, - HeaderValue::from_str(&format!("{}", stream.len())).context("bug")?, - ); } + } else { + output_headers.insert( + http::header::CONTENT_LENGTH, + HeaderValue::from_str(&format!("{}", stream.len())).context("bug")?, + ); } let s = tokio_util::io::ReaderStream::new(stream); diff --git a/crates/librqbit/webui/src/components/FileListInput.tsx b/crates/librqbit/webui/src/components/FileListInput.tsx index 9ec54102..323ba1c0 100644 --- a/crates/librqbit/webui/src/components/FileListInput.tsx +++ b/crates/librqbit/webui/src/components/FileListInput.tsx @@ -158,8 +158,7 @@ const FileTreeComponent: React.FC<{ const fileLink = (file: TorrentFileForCheckbox) => { if ( allowStream && - torrentId != null && - /\.(mp4|mkv|avi)$/.test(file.filename) + torrentId != null ) { return API.getTorrentStreamUrl(torrentId, file.id, file.filename); } diff --git a/crates/librqbit/webui/src/components/forms/FormCheckbox.tsx b/crates/librqbit/webui/src/components/forms/FormCheckbox.tsx index dbe4f12a..f2e0a472 100644 --- a/crates/librqbit/webui/src/components/forms/FormCheckbox.tsx +++ b/crates/librqbit/webui/src/components/forms/FormCheckbox.tsx @@ -39,6 +39,7 @@ export const FormCheckbox: React.FC<{ {labelLink ? ( {label}