Skip to content

Commit cf88469

Browse files
Automatically decode chunked encoding
1 parent abca149 commit cf88469

File tree

1 file changed

+52
-6
lines changed

1 file changed

+52
-6
lines changed

libraries/common/io/requests.effekt

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,10 @@ namespace plain {
440440
do emit(do read[Byte]())
441441
} with stop { () => do raise(WrongFormat(), "Premature end of chunk in chunked encoding") }
442442
}
443+
expect("CRLF at end of chunk"){
444+
readIf('\r')
445+
readIf('\n')
446+
}
443447
}
444448
// trailer headers
445449
expect("Trailers or end of response"){
@@ -454,7 +458,7 @@ namespace plain {
454458
}
455459

456460
/// 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.
458462
def parseResponse[R]{ body: {ResponseReader} => R }: R / { read[Byte], Exception[WrongFormat] } = {
459463
with decodeUTF8
460464
with returning::scanner[Char, R]
@@ -479,19 +483,54 @@ namespace plain {
479483
readIf('\n')
480484
}
481485

486+
def lower(s: String) = collectString{ for{ s.each }{ c => do emit(c.toLower) } }
487+
482488
def getHeader(key: String) = {
483489
var r = None()
484490
headers.foreach {
485-
case (k, v) and k == key => r = Some(v)
491+
case (k, v) and k.lower == key => r = Some(v)
486492
case _ => ()
487493
}
488494
r
489495
}
490496

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+
}
495534
}
496535
}
497536
}
@@ -694,12 +733,19 @@ namespace example {
694733
crlf()
695734
"content-type: text/plain".each;
696735
crlf()
736+
"Transfer-Encoding: chunked".each
737+
crlf()
697738
"x-multiline-test-header: This is the first line".each
698739
crlf()
699740
" and this is the second line".each
700741
crlf()
701742
crlf()
743+
"6".each
744+
crlf()
702745
"Hello!".each
746+
crlf()
747+
"0".each
748+
crlf()
703749
}
704750
with def res = plain::parseResponse
705751
println(res.status())

0 commit comments

Comments
 (0)