From b94a3a1e17ffcf8864a7d61c55d45fd915b41e5d Mon Sep 17 00:00:00 2001 From: _ <> Date: Sun, 29 Nov 2020 22:31:54 +0000 Subject: [PATCH] Move byte range code into its own file --- crates/ptth_server/src/file_server/mod.rs | 106 +++----------------- crates/ptth_server/src/file_server/range.rs | 75 ++++++++++++++ crates/ptth_server/src/file_server/tests.rs | 53 ++++++---- 3 files changed, 127 insertions(+), 107 deletions(-) create mode 100644 crates/ptth_server/src/file_server/range.rs diff --git a/crates/ptth_server/src/file_server/mod.rs b/crates/ptth_server/src/file_server/mod.rs index 3e74509..be681ac 100644 --- a/crates/ptth_server/src/file_server/mod.rs +++ b/crates/ptth_server/src/file_server/mod.rs @@ -32,8 +32,6 @@ use tokio::{ }; use tracing::instrument; -use regex::Regex; - #[cfg (test)] use always_equal::test::AlwaysEqual; @@ -51,11 +49,17 @@ use ptth_core::{ }; pub mod errors; +mod range; use errors::{ FileServerError, MarkdownError, }; +use range::{ + check_range, + ParsedRange, + ValidParsedRange, +}; mod emoji { pub const VIDEO: &str = "\u{1f39e}\u{fe0f}"; @@ -100,71 +104,6 @@ struct TemplateDirPage <'a> { entries: Vec , } -fn parse_range_header (range_str: &str) -> (Option , Option ) { - use lazy_static::lazy_static; - - lazy_static! { - static ref RE: Regex = Regex::new (r"^bytes=(\d*)-(\d*)$").expect ("Couldn't compile regex for Range header"); - } - - debug! ("{}", range_str); - - let caps = match RE.captures (range_str) { - Some (x) => x, - None => return (None, None), - }; - let start = caps.get (1).map (|x| x.as_str ()); - let end = caps.get (2).map (|x| x.as_str ()); - - let start = start.and_then (|x| u64::from_str_radix (x, 10).ok ()); - - // HTTP specifies ranges as [start inclusive, end inclusive] - // But that's dumb and [start inclusive, end exclusive) is better - - let end = end.and_then (|x| u64::from_str_radix (x, 10).ok ().map (|x| x + 1)); - - (start, end) -} - -use std::ops::Range; - -#[derive (Debug, PartialEq)] -enum ParsedRange { - Ok (Range ), - PartialContent (Range ), - RangeNotSatisfiable (u64), -} - -fn check_range (range_str: Option <&str>, file_len: u64) - -> ParsedRange -{ - use ParsedRange::*; - - let not_satisfiable = RangeNotSatisfiable (file_len); - - let range_str = match range_str { - None => return Ok (0..file_len), - Some (x) => x, - }; - - let (start, end) = parse_range_header (range_str); - - let start = start.unwrap_or (0); - if start >= file_len { - return not_satisfiable; - } - - let end = end.unwrap_or (file_len); - if end > file_len { - return not_satisfiable; - } - if end < start { - return not_satisfiable; - } - - PartialContent (start..end) -} - fn get_icon (file_name: &str) -> &'static str { if file_name.ends_with (".mp4") || @@ -289,8 +228,7 @@ async fn serve_dir ( async fn serve_file ( mut f: File, should_send_body: bool, - range: Range , - range_requested: bool + range: ValidParsedRange ) -> Result { @@ -302,6 +240,8 @@ async fn serve_file ( None }; + let (range, range_requested) = (range.range, range.range_requested); + info! ("Serving range {}-{}", range.start, range.end); let content_length = range.end - range.start; @@ -435,8 +375,7 @@ struct ServeDirParams { #[derive (Debug, PartialEq)] struct ServeFileParams { send_body: bool, - range: Range , - range_requested: bool, + range: ValidParsedRange, file: AlwaysEqual , } @@ -508,8 +447,12 @@ async fn internal_serve_file ( Ok (match check_range (range_header, file_len) { ParsedRange::RangeNotSatisfiable (file_len) => InternalResponse::RangeNotSatisfiable (file_len), - ParsedRange::Ok (range) => { + ParsedRange::Valid (range) => { if uri.query () == Some ("as_markdown") { + if range.range_requested { + return Ok (InternalResponse::InvalidQuery); + } + const MAX_BUF_SIZE: u32 = 1_000_000; if file_len > MAX_BUF_SIZE.into () { InternalResponse::MarkdownErr (MarkdownError::TooBig) @@ -529,22 +472,6 @@ async fn internal_serve_file ( file, send_body, range, - range_requested: false, - }) - } - }, - ParsedRange::PartialContent (range) => { - if uri.query ().is_some () { - InternalResponse::InvalidQuery - } - else { - let file = file.into (); - - InternalResponse::ServeFile (ServeFileParams { - file, - send_body, - range, - range_requested: true, }) } }, @@ -670,8 +597,7 @@ pub async fn serve_all ( file, send_body, range, - range_requested, - }) => serve_file (file.into_inner (), send_body, range, range_requested).await?, + }) => serve_file (file.into_inner (), send_body, range).await?, MarkdownErr (e) => match e { MarkdownError::TooBig => serve_error (StatusCode::InternalServerError, "File is too big to preview as Markdown"), //MarkdownError::NotMarkdown => serve_error (StatusCode::BadRequest, "File is not Markdown"), diff --git a/crates/ptth_server/src/file_server/range.rs b/crates/ptth_server/src/file_server/range.rs new file mode 100644 index 0000000..2fe7fad --- /dev/null +++ b/crates/ptth_server/src/file_server/range.rs @@ -0,0 +1,75 @@ +use std::ops::Range; + +use regex::Regex; + +pub fn parse_range_header (range_str: &str) -> (Option , Option ) { + use lazy_static::lazy_static; + + lazy_static! { + static ref RE: Regex = Regex::new (r"^bytes=(\d*)-(\d*)$").expect ("Couldn't compile regex for Range header"); + } + + let caps = match RE.captures (range_str) { + Some (x) => x, + None => return (None, None), + }; + let start = caps.get (1).map (|x| x.as_str ()); + let end = caps.get (2).map (|x| x.as_str ()); + + let start = start.and_then (|x| u64::from_str_radix (x, 10).ok ()); + + // HTTP specifies ranges as [start inclusive, end inclusive] + // But that's dumb and [start inclusive, end exclusive) is better + + let end = end.and_then (|x| u64::from_str_radix (x, 10).ok ().map (|x| x + 1)); + + (start, end) +} + +#[derive (Debug, PartialEq)] +pub struct ValidParsedRange { + pub range: Range , + pub range_requested: bool, +} + +#[derive (Debug, PartialEq)] +pub enum ParsedRange { + Valid (ValidParsedRange), + RangeNotSatisfiable (u64), +} + +pub fn check_range (range_str: Option <&str>, file_len: u64) + -> ParsedRange +{ + use ParsedRange::*; + + let not_satisfiable = RangeNotSatisfiable (file_len); + + let range_str = match range_str { + None => return Valid (ValidParsedRange { + range: 0..file_len, + range_requested: false, + }), + Some (x) => x, + }; + + let (start, end) = parse_range_header (range_str); + + let start = start.unwrap_or (0); + if start >= file_len { + return not_satisfiable; + } + + let end = end.unwrap_or (file_len); + if end > file_len { + return not_satisfiable; + } + if end < start { + return not_satisfiable; + } + + Valid (ValidParsedRange { + range: start..end, + range_requested: true, + }) +} diff --git a/crates/ptth_server/src/file_server/tests.rs b/crates/ptth_server/src/file_server/tests.rs index aa1a541..3970c8b 100644 --- a/crates/ptth_server/src/file_server/tests.rs +++ b/crates/ptth_server/src/file_server/tests.rs @@ -34,33 +34,46 @@ fn icons () { #[test] fn parse_range_header () { + use super::range::{ + *, + ParsedRange::*, + }; + for (input, expected) in vec! [ ("", (None, None)), ("bytes=0-", (Some (0), None)), ("bytes=0-999", (Some (0), Some (1000))), ("bytes=111-999", (Some (111), Some (1000))), ].into_iter () { - let actual = super::parse_range_header (input); + let actual = parse_range_header (input); assert_eq! (actual, expected); } - use super::ParsedRange::*; + let ok_range = |range| Valid (ValidParsedRange { + range, + range_requested: false, + }); + + let partial_content = |range| Valid (ValidParsedRange { + range, + range_requested: true, + }); for (header, file_len, expected) in vec! [ - (None, 0, Ok (0..0)), - (None, 1024, Ok (0..1024)), + (None, 0, ok_range (0..0)), + (None, 1024, ok_range (0..1024)), (Some (""), 0, RangeNotSatisfiable (0)), - (Some (""), 1024, PartialContent (0..1024)), + (Some (""), 1024, partial_content (0..1024)), - (Some ("bytes=0-"), 1024, PartialContent (0..1024)), - (Some ("bytes=0-999"), 1024, PartialContent (0..1000)), - (Some ("bytes=0-1023"), 1024, PartialContent (0..1024)), - (Some ("bytes=111-999"), 1024, PartialContent (111..1000)), - (Some ("bytes=111-1023"), 1024, PartialContent (111..1024)), + (Some ("bytes=0-"), 1024, partial_content (0..1024)), + (Some ("bytes=0-999"), 1024, partial_content (0..1000)), + (Some ("bytes=0-1023"), 1024, partial_content (0..1024)), + (Some ("bytes=111-999"), 1024, partial_content (111..1000)), + (Some ("bytes=111-1023"), 1024, partial_content (111..1024)), (Some ("bytes=200-100"), 1024, RangeNotSatisfiable (1024)), - (Some ("bytes=0-"), 512, PartialContent (0..512)), + (Some ("bytes=0-"), 512, partial_content (0..512)), (Some ("bytes=0-1023"), 512, RangeNotSatisfiable (512)), (Some ("bytes=1000-1023"), 512, RangeNotSatisfiable (512)), ].into_iter () { @@ -139,14 +152,18 @@ fn file_server () { ("/files/src/?", InvalidQuery), (bad_passwords_path, ServeFile (ServeFileParams { send_body: true, - range: 0..1_048_576, - range_requested: false, + range: ValidParsedRange { + range: 0..1_048_576, + range_requested: false, + }, file: AlwaysEqual::testing_blank (), })), ("/files/test/test.md", ServeFile (ServeFileParams { send_body: true, - range: 0..144, - range_requested: false, + range: ValidParsedRange { + range: 0..144, + range_requested: false, + }, file: AlwaysEqual::testing_blank (), })), ] { @@ -200,8 +217,10 @@ fn file_server () { assert_eq! (resp.expect ("Should be Ok (_)"), ServeFile (ServeFileParams { send_body: false, - range: 0..1_048_576, - range_requested: false, + range: ValidParsedRange { + range: 0..1_048_576, + range_requested: false, + }, file: AlwaysEqual::testing_blank (), })); }