This port is of limited use if it cannot be used reliably. Rather than behaving as if the input has finished when it ends unexpectedly, instead raise an exception.
* module/web/http.scm (make-chunked-input-port): Raise an exception on premature termination. (&chunked-input-ended-prematurely): New exception type. (chunked-input-ended-prematurely-error?): New procedure. * test-suite/tests/web-http.test (pass-if-named-exception): Rename to pass-if-named-exception. (pass-if-named-exception): New syntax. ("Exception on premature chunk end"): New test for this behaviour. --- doc/ref/web.texi | 3 +++ module/web/http.scm | 18 ++++++++++++++++-- test-suite/tests/web-http.test | 25 +++++++++++++++++-------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/doc/ref/web.texi b/doc/ref/web.texi index 93cd0214f..b04d328b7 100644 --- a/doc/ref/web.texi +++ b/doc/ref/web.texi @@ -1117,6 +1117,9 @@ Returns a new port, that transparently reads and decodes chunk-encoded data from @var{port}. If no more chunk-encoded data is available, it returns the end-of-file object. When the port is closed, @var{port} will also be closed, unless @var{keep-alive?} is true. + +If the chunked input ends prematurely, a +@code{&chunked-input-ended-promaturely} exception will be raised. @end deffn @example diff --git a/module/web/http.scm b/module/web/http.scm index 163c52176..b3ea076ae 100644 --- a/module/web/http.scm +++ b/module/web/http.scm @@ -38,6 +38,7 @@ #:use-module (ice-9 q) #:use-module (ice-9 binary-ports) #:use-module (ice-9 textual-ports) + #:use-module (ice-9 exceptions) #:use-module (rnrs bytevectors) #:use-module (web uri) #:export (string->header @@ -67,6 +68,8 @@ read-response-line write-response-line + &chunked-input-error-prematurely + chunked-input-ended-prematurely-error? make-chunked-input-port make-chunked-output-port @@ -1935,6 +1938,17 @@ treated specially, and is just returned as a plain string." ;; Chunked Responses +(define &chunked-input-ended-prematurely + (make-exception-type '&chunked-input-error-prematurely + &external-error + '())) + +(define make-chunked-input-ended-prematurely-error + (record-constructor &chunked-input-ended-prematurely)) + +(define chunked-input-ended-prematurely-error? + (record-predicate &chunked-input-ended-prematurely)) + (define (read-chunk-header port) "Read a chunk header from PORT and return the size in bytes of the upcoming chunk." @@ -1987,8 +2001,8 @@ closed it will also close PORT, unless the KEEP-ALIVE? is true." ask-for))) (cond ((eof-object? read) ;premature termination - (set! finished? #t) - num-read) + (raise-exception + (make-chunked-input-ended-prematurely-error))) (else (let ((left (- remaining read))) (set! remaining left) diff --git a/test-suite/tests/web-http.test b/test-suite/tests/web-http.test index 86fbc0e1a..796c44dd9 100644 --- a/test-suite/tests/web-http.test +++ b/test-suite/tests/web-http.test @@ -28,16 +28,19 @@ #:use-module (test-suite lib)) -(define-syntax pass-if-named-exception +(define-syntax pass-if-expected-exception (syntax-rules () - ((_ name k pat exp) + ((_ name exception-predicate? exp) (pass-if name - (catch 'k - (lambda () exp (error "expected exception" 'k)) - (lambda (k message args) - (if (string-match pat message) - #t - (error "unexpected exception" message args)))))))) + (with-exception-handler + (lambda (exn) + (if (exception-predicate? exn) + #t + (error "unexpected exception" exn))) + (lambda () + exp + #f) + #:unwind? #t))))) (define-syntax pass-if-only-parse (syntax-rules () @@ -486,6 +489,12 @@ (port (make-chunked-input-port (open-input-string str)))) (get-string-all port))) + (pass-if-expected-exception "Exception on premature chunk end" + chunked-input-ended-prematurely-error? + (let* ((str "b\r\nFirst chunk\r\nc\r\nSecond chun") + (port (make-chunked-input-port (open-input-string str)))) + (get-string-all port))) + (pass-if-equal (call-with-output-string (lambda (out-raw) -- 2.36.1