@@ -440,6 +440,10 @@ namespace plain {
440
440
do emit(do read[Byte]())
441
441
} with stop { () => do raise(WrongFormat(), "Premature end of chunk in chunked encoding") }
442
442
}
443
+ expect("CRLF at end of chunk"){
444
+ readIf('\r')
445
+ readIf('\n')
446
+ }
443
447
}
444
448
// trailer headers
445
449
expect("Trailers or end of response"){
@@ -454,7 +458,7 @@ namespace plain {
454
458
}
455
459
456
460
/// Parse a HTTP response from its byte representation.
457
- /// Does not decode transfer encodings, will return everything as the body until eof (unless stopped first)
461
+ /// Decodes chunked encoding and stops after Content-Length, if provided.
458
462
def parseResponse[R]{ body: {ResponseReader} => R }: R / { read[Byte], Exception[WrongFormat] } = {
459
463
with decodeUTF8
460
464
with returning::scanner[Char, R]
@@ -479,19 +483,54 @@ namespace plain {
479
483
readIf('\n')
480
484
}
481
485
486
+ def lower(s: String) = collectString{ for{ s.each }{ c => do emit(c.toLower) } }
487
+
482
488
def getHeader(key: String) = {
483
489
var r = None()
484
490
headers.foreach {
485
- case (k, v) and k == key => r = Some(v)
491
+ case (k, v) and k.lower == key => r = Some(v)
486
492
case _ => ()
487
493
}
488
494
r
489
495
}
490
496
491
- try body{res} with res: ResponseReader {
492
- def status() = resume(statusCode)
493
- def getHeader(key) = resume(getHeader(key))
494
- def body() = resume { exhaustively{ do emit(do read[Byte]()) } }
497
+ val transferEnc = getHeader("transfer-encoding")
498
+ val contentLength = getHeader("content-length")
499
+
500
+ (transferEnc, contentLength) match {
501
+ case (_, Some(l)) and l.toInt is len => // use provided content-length
502
+ try body{res} with res: ResponseReader {
503
+ def status() = resume(statusCode)
504
+ def getHeader(key) = resume(getHeader(key.lower))
505
+ def body() = resume { limit[Byte](len){ exhaustively{ do emit(do read[Byte]()) } } }
506
+ }
507
+ case (Some(enc), _) and enc == "chunked" => // decode chunked encoding // TODO support multiple and remove
508
+ var finalLength = None()
509
+ try body{res} with res: ResponseReader {
510
+ def status() = resume(statusCode)
511
+ def getHeader(key) = {
512
+ if(key.lower == "content-length") {
513
+ resume(finalLength)
514
+ } else if(key.lower == "transfer-encoding") {
515
+ resume(None())
516
+ } else {
517
+ resume(getHeader(key.lower))
518
+ }
519
+ }
520
+ def body() = {
521
+ resume {
522
+ headers = headers.append(collectList[(String, String)]{
523
+ finalLength = Some(unchunk().show)
524
+ })
525
+ }
526
+ }
527
+ }
528
+ case (_, _) => // read until end of input
529
+ try body{res} with res: ResponseReader {
530
+ def status() = resume(statusCode)
531
+ def getHeader(key) = resume(getHeader(key.lower))
532
+ def body() = resume { exhaustively{ do emit(do read[Byte]()) } }
533
+ }
495
534
}
496
535
}
497
536
}
@@ -694,12 +733,19 @@ namespace example {
694
733
crlf()
695
734
"content-type: text/plain".each;
696
735
crlf()
736
+ "Transfer-Encoding: chunked".each
737
+ crlf()
697
738
"x-multiline-test-header: This is the first line".each
698
739
crlf()
699
740
" and this is the second line".each
700
741
crlf()
701
742
crlf()
743
+ "6".each
744
+ crlf()
702
745
"Hello!".each
746
+ crlf()
747
+ "0".each
748
+ crlf()
703
749
}
704
750
with def res = plain::parseResponse
705
751
println(res.status())
0 commit comments