char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f4a0f5b
main
1use std:: fs:: File ; 2use std:: io; 3# [ cfg ( not ( any ( unix , windows )))] 4use std:: io::{ Read , Seek , SeekFrom }; 5# [ cfg ( unix )] 6use std:: os:: unix:: fs:: FileExt ; 7# [ cfg ( windows )] 8use std:: os:: windows:: fs:: FileExt ; 9use std:: sync:: atomic::{ AtomicBool , Ordering }; 10use std:: sync::{ Arc , Mutex }; 11use std:: time::{ Duration , SystemTime , UNIX_EPOCH }; 12 13use hyper:: http:: header::{ HeaderName , HeaderValue }; 14use hyper:: http::{ HeaderMap , Response }; 15use tokio:: runtime:: Handle ; 16 17pub type CheckFile =Arc < dyn Fn () -> io:: Result <()> +Send +Sync >; 18pub type HeaderPairs =Vec <( String , String )>; 19const SAFE_INTEGER : u64 =( 1 <<53 ) -1 ; 20 21struct Source { 22file : File , 23offset : u64 , 24remaining : u64 , 25check : CheckFile , 26} 27 28pub struct FileBody { 29runtime : Handle , 30source : Mutex < Option < Source >>, 31closed : AtomicBool , 32length : u64 , 33} 34 35impl FileBody { 36pub fn len ( & self ) ->u64 { 37self . length 38} 39 40pub fn is_empty ( & self ) ->bool { 41self . length ==0 42} 43 44pub async fn read ( self : & Arc < Self >) -> io:: Result <( Vec < u8 >, bool )> { 45let body =self . clone (); 46self . runtime 47. spawn_blocking ( move ||{ 48let mut source = body. source . lock (). unwrap (); 49let result =( ||{ 50if body. closed . load ( Ordering :: Acquire ) { 51return Err ( io:: Error :: new ( 52 io:: ErrorKind :: NotConnected , 53"File response is closed" , 54)); 55} 56let source = source. as_mut (). ok_or_else ( ||{ 57 io:: Error :: new ( io:: ErrorKind :: NotConnected , "File response is closed" ) 58}) ?; 59( source. check )() ?; 60let mut bytes =vec! [ 0 ; source. remaining . min ( 65536 ) as usize ]; 61# [ cfg ( unix )] 62let count = source. file . read_at ( & mut bytes, source. offset ) ?; 63# [ cfg ( windows )] 64let count = source. file . seek_read ( & mut bytes, source. offset ) ?; 65# [ cfg ( not ( any ( unix , windows )))] 66let count ={ 67 source. file . seek ( SeekFrom :: Start ( source. offset )) ?; 68 source. file . read ( & mut bytes) ? 69}; 70if count ==0 { 71return Err ( io:: Error :: new ( 72 io:: ErrorKind :: UnexpectedEof , 73"File ended before Content-Length" , 74)); 75} 76if body. closed . load ( Ordering :: Acquire ) { 77return Err ( io:: Error :: new ( 78 io:: ErrorKind :: NotConnected , 79"File response is closed" , 80)); 81} 82 bytes. truncate ( count); 83 source. offset += countas u64 ; 84 source. remaining -= countas u64 ; 85Ok (( bytes, source. remaining ==0 )) 86})(); 87if result. as_ref (). map_or ( true , |( _, done) |* done) { 88 body. closed . store ( true , Ordering :: Release ); 89 source. take (); 90} 91 result 92}) 93. await 94. map_err ( io:: Error :: other) ? 95} 96 97pub fn close ( self : & Arc < Self >) { 98if !self . closed . swap ( true , Ordering :: AcqRel ) { 99let body =self . clone (); 100self . runtime . spawn_blocking ( move ||{ 101 body. source . lock (). unwrap (). take (); 102}); 103} 104} 105} 106 107impl Drop for FileBody { 108fn drop ( & mut self ) { 109// File descriptors are closed off the guest thread, including abandoned responses. 110let source =self . source . get_mut (). unwrap (). take (); 111if source. is_some () { 112self . runtime . spawn_blocking ( move ||drop ( source)); 113} 114} 115} 116 117pub fn headers ( pairs : HeaderPairs ) -> io:: Result < HeaderMap > { 118let mut headers =HeaderMap :: new (); 119for ( name, value) in pairs{ 120let name =HeaderName :: from_bytes ( name. as_bytes ()). map_err ( io:: Error :: other) ?; 121let value = value 122. chars () 123. map ( |c| u8:: try_from ( cas u32 )) 124. collect ::< Result < Vec < _ >, _ >>() 125. map_err ( io:: Error :: other) ?; 126let value =HeaderValue :: from_bytes ( & value). map_err ( io:: Error :: other) ?; 127 headers. append ( name, value); 128} 129Ok ( headers) 130} 131 132pub fn header_pairs ( headers : & HeaderMap ) ->HeaderPairs { 133 headers 134. iter () 135. map ( |( name, value) |{ 136( 137 name. to_string (), 138 value 139. as_bytes () 140. iter () 141. map ( |byte| char:: from ( * byte)) 142. collect (), 143) 144}) 145. collect () 146} 147 148fn set ( headers : & mut HeaderMap , name : &' static str , value : impl ToString ) -> io:: Result <()> { 149 headers. insert ( 150 name, 151HeaderValue :: from_str ( & value. to_string ()). map_err ( io:: Error :: other) ?, 152); 153Ok (()) 154} 155 156fn get ( headers : & HeaderMap , name : & str ) ->Option < Vec < u8 >> { 157let mut values = headers. get_all ( name). iter (); 158let mut value = values. next () ?. as_bytes (). to_vec (); 159for nextin values{ 160 value. extend_from_slice ( b", " ); 161 value. extend_from_slice ( next. as_bytes ()); 162} 163Some ( value) 164} 165 166fn tags ( value : & [ u8 ]) ->Option < Vec < & [ u8 ]>> { 167let mut value = value. trim_ascii (); 168let mut tags =Vec :: new (); 169while !value. is_empty () { 170if value[ 0 ] ==b',' { 171 value = value[ 1 ..]. trim_ascii_start (); 172continue ; 173} 174let original = value; 175if value. starts_with ( b"W/" ) { 176 value =& value[ 2 ..]; 177} 178if value. first () !=Some ( & b'"' ) { 179return None ; 180} 181let end = value[ 1 ..]. iter (). position ( |byte|* byte ==b'"' ) ? +1 ; 182if !value[ 1 ..end] 183. iter () 184. all ( |byte|* byte ==0x21 ||* byte >=0x23 &&* byte !=0x7f ) 185{ 186return None ; 187} 188let length = original. len () - value. len () + end +1 ; 189 tags. push ( & original[ ..length]); 190 value = value[ end +1 ..]. trim_ascii_start (); 191if !value. is_empty () && value[ 0 ] !=b',' { 192return None ; 193} 194} 195Some ( tags) 196} 197 198fn matches ( value : & [ u8 ], etag : Option < & [ u8 ]>, weak : bool ) ->bool { 199if value ==b"*" { 200return true ; 201} 202let Some ( etag) = etagelse { 203return false ; 204}; 205tags ( value). is_some_and ( |tags|{ 206 tags. iter (). any ( |tag|{ 207if weak{ 208 tag. strip_prefix ( b"W/" ). unwrap_or ( tag) == etag. strip_prefix ( b"W/" ). unwrap_or ( etag) 209} else { 210 !tag. starts_with ( b"W/" ) &&* tag == etag 211} 212}) 213}) 214} 215 216fn date ( value : & [ u8 ]) ->Option < SystemTime > { 217 httpdate:: parse_http_date ( std:: str:: from_utf8 ( value). ok () ?). ok () 218} 219 220# [ derive ( Debug , PartialEq )] 221enum Range { 222Ignore , 223Unsatisfiable , 224Bytes ( u64 , u64 ), 225} 226 227fn range ( value : & [ u8 ], size : u64 ) ->Range { 228let Some ( equal) = value. iter (). position ( |byte|* byte ==b'=' ) else { 229return Range :: Ignore ; 230}; 231let ( unit, interval) =( & value[ ..equal], & value[ equal +1 ..]); 232if !unit. eq_ignore_ascii_case ( b"bytes" ) { 233return Range :: Ignore ; 234} 235let Some ( dash) = interval. iter (). position ( |byte|* byte ==b'-' ) else { 236return Range :: Ignore ; 237}; 238let ( start, end) =( & interval[ ..dash], & interval[ dash +1 ..]); 239if ( start. is_empty () && end. is_empty ()) || !start. iter (). chain ( end). all ( u8:: is_ascii_digit) { 240return Range :: Ignore ; 241} 242let normalize = |value : & [ u8 ] |{ 243 value 244. iter () 245. position ( |byte|* byte !=b'0' ) 246. unwrap_or ( value. len ()) 247}; 248let start =& start[ normalize ( start) ..]; 249let end =& end[ normalize ( end) ..]; 250let parse = |value : & [ u8 ] |{ 251 value. iter (). try_fold ( 0u64 , |number, digit|{ 252 number. checked_mul ( 10 ) ?. checked_add (( digit -b'0' ) as u64 ) 253}) 254}; 255if interval. starts_with ( b"-" ) { 256let suffix =parse ( end). unwrap_or ( u64:: MAX ); 257return if suffix ==0 || size ==0 { 258Range :: Unsatisfiable 259} else { 260Range :: Bytes ( size. saturating_sub ( suffix), size -1 ) 261}; 262} 263// Compare decimal strings before conversion so oversized reversed intervals remain malformed. 264if !interval. ends_with ( b"-" ) &&( end. len (), end) <( start. len (), start) { 265return Range :: Ignore ; 266} 267let Some ( start) =parse ( start). filter ( |start|* start < size) else { 268return Range :: Unsatisfiable ; 269}; 270Range :: Bytes ( 271 start, 272parse ( end) 273. filter ( |_| !interval. ends_with ( b"-" )) 274. unwrap_or ( u64:: MAX ) 275. min ( size -1 ), 276) 277} 278 279pub fn serve_file ( 280runtime : Handle , 281method : & str , 282request : HeaderMap , 283path : & str , 284headers : HeaderMap , 285open : impl FnOnce () -> io:: Result <( File , CheckFile )>, 286) -> io:: Result < Response < Option < Arc < FileBody >>>> { 287let mut response =Response :: new ( None ); 288* response. headers_mut () = headers; 289let headers = response. headers_mut (); 290for namein [ "content-length" , "content-range" , "last-modified" ] { 291 headers. remove ( name); 292} 293if method !="GET" && method !="HEAD" { 294set ( headers, "allow" , "GET, HEAD" ) ?; 295set ( headers, "content-length" , 0 ) ?; 296* response. status_mut () = hyper:: StatusCode :: METHOD_NOT_ALLOWED ; 297return Ok ( response); 298} 299let ( file, check) =match open () { 300Ok ( opened) => opened, 301Err ( error) 302if matches! ( 303 error. kind (), 304 io:: ErrorKind :: NotFound | io:: ErrorKind :: NotADirectory 305) => 306{ 307set ( headers, "content-length" , 0 ) ?; 308* response. status_mut () = hyper:: StatusCode :: NOT_FOUND ; 309return Ok ( response); 310} 311Err ( error) =>return Err ( error), 312}; 313check () ?; 314let info = file. metadata () ?; 315if !info. is_file () { 316set ( headers, "content-length" , 0 ) ?; 317* response. status_mut () = hyper:: StatusCode :: NOT_FOUND ; 318return Ok ( response); 319} 320if info. len () >SAFE_INTEGER { 321return Err ( io:: Error :: other ( "Invalid file size" )); 322} 323let modified = info. modified (). ok (); 324// httpdate only formats years 1970 through 9999. 325let modified_seconds = modified 326. and_then ( |date| date. duration_since ( UNIX_EPOCH ). ok ()) 327. filter ( |duration| duration. as_secs () <253402300800 ) 328. map ( |duration|UNIX_EPOCH +Duration :: from_secs ( duration. as_secs ())); 329if let Some ( modified) = modified 330 && !headers. contains_key ( "etag" ) 331{ 332let millis =match modified. duration_since ( UNIX_EPOCH ) { 333Ok ( duration) => duration. as_millis () as i128 , 334Err ( error) => -( error. duration (). as_millis () as i128 ), 335}; 336set ( headers, "etag" , format! ( "W/\"{}-{millis}\"" , info. len ())) ?; 337} 338if let Some ( modified) = modified_seconds{ 339set ( headers, "last-modified" , httpdate:: fmt_http_date ( modified)) ?; 340} 341let etag =get ( headers, "etag" ); 342if !headers. contains_key ( "content-type" ) { 343let mime = mime_guess:: from_path ( path). first_or_octet_stream (); 344let text = mime. type_ () =="text" 345 ||matches! ( 346 mime. essence_str (), 347"application/javascript" |"application/json" |"application/xml" |"image/svg+xml" 348); 349set ( 350 headers, 351"content-type" , 352format! ( "{mime}{}" , if text{ "; charset=utf-8" } else { "" }), 353) ?; 354} 355set ( headers, "accept-ranges" , "bytes" ) ?; 356let mut status =200 ; 357if let Some ( value) =get ( & request, "if-match" ) { 358if !matches ( & value, etag. as_deref (), false ) { 359 status =412 ; 360} 361} else if let Some ( value) =get ( & request, "if-unmodified-since" ) 362 &&let ( Some ( date), Some ( modified)) =( date ( & value), modified_seconds) 363 && modified > date 364{ 365 status =412 ; 366} 367if status ==200 { 368if let Some ( value) =get ( & request, "if-none-match" ) { 369if matches ( & value, etag. as_deref (), true ) { 370 status =304 ; 371} 372} else if let Some ( value) =get ( & request, "if-modified-since" ) 373 &&let ( Some ( date), Some ( modified)) =( date ( & value), modified_seconds) 374 && modified <= date 375{ 376 status =304 ; 377} 378} 379if status !=200 { 380if status ==412 { 381set ( headers, "content-length" , 0 ) ?; 382} 383* response. status_mut () = hyper:: StatusCode :: from_u16 ( status). unwrap (); 384return Ok ( response); 385} 386 387let mut start =0 ; 388let mut length = info. len (); 389let if_range =get ( & request, "if-range" ); 390let use_range = if_range. as_ref (). is_none_or ( |value|{ 391if value. starts_with ( b"\"" ) || value. starts_with ( b"W/\"" ) { 392 !value. starts_with ( b"W/" ) 393 && etag 394. as_deref () 395. is_some_and ( |etag| !etag. starts_with ( b"W/" ) && etag == value) 396} else { 397date ( value). is_some_and ( |date|Some ( date) == modified_seconds) 398} 399}); 400if method =="GET" 401 && use_range 402 &&let Some ( value) =get ( & request, "range" ) 403{ 404match range ( & value, info. len ()) { 405Range :: Ignore =>{} 406Range :: Unsatisfiable =>{ 407 status =416 ; 408 length =0 ; 409set ( headers, "content-range" , format! ( "bytes */{}" , info. len ())) ?; 410} 411Range :: Bytes ( first, last) =>{ 412 status =206 ; 413 start = first; 414 length = last - first +1 ; 415set ( 416 headers, 417"content-range" , 418format! ( "bytes {first}-{last}/{}" , info. len ()), 419) ?; 420} 421} 422} 423set ( headers, "content-length" , length) ?; 424if method =="GET" && length !=0 { 425* response. body_mut () =Some ( Arc :: new ( FileBody { 426 runtime, 427source : Mutex :: new ( Some ( Source { 428 file, 429offset : start, 430remaining : length, 431 check, 432})), 433closed : AtomicBool :: new ( false ), 434 length, 435})); 436} 437* response. status_mut () = hyper:: StatusCode :: from_u16 ( status). unwrap (); 438Ok ( response) 439} 440 441# [ cfg ( test )] 442mod tests{ 443use super :: * ; 444 445# [ test ] 446fn ranges_handle_overflow_and_reversed_intervals () { 447assert_eq! ( 448range ( b"bytes=9007199254740993-9007199254740992" , 10 ), 449Range :: Ignore 450); 451assert_eq! ( 452range ( 453b"bytes=999999999999999999999999-999999999999999999999998" , 45410 455), 456Range :: Ignore 457); 458assert_eq! ( 459range ( b"bytes=999999999999999999999999-" , 10 ), 460Range :: Unsatisfiable 461); 462assert_eq! ( 463range ( b"bytes=8-999999999999999999999999" , 10 ), 464Range :: Bytes ( 8 , 9 ) 465); 466assert_eq! ( 467range ( b"bytes=-999999999999999999999999" , 10 ), 468Range :: Bytes ( 0 , 9 ) 469); 470assert_eq! ( range ( b"bytes=0002-0004" , 10 ), Range :: Bytes ( 2 , 4 )); 471assert_eq! ( range ( b"bytes=0-" , 0 ), Range :: Unsatisfiable ); 472assert_eq! ( range ( b"bytes=-0" , 10 ), Range :: Unsatisfiable ); 473assert_eq! ( range ( b"bytes=0-1,4-5" , 10 ), Range :: Ignore ); 474} 475 476# [ test ] 477fn entity_tag_lists_preserve_commas_and_comparison_strength () { 478assert! ( matches ( 479b", \"other,tag\", W/\"known\"," , 480Some ( b"\"known\"" ), 481true 482)); 483assert! ( !matches ( b"W/\"known\"" , Some ( b"W/\"known\"" ), false )); 484assert! ( matches ( b"\"known\"" , Some ( b"\"known\"" ), false )); 485assert! ( matches ( b"*" , None , false )); 486assert! ( !matches ( b"junk, \"known\"" , Some ( b"\"known\"" ), true )); 487} 488}