Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions http.carp
Original file line number Diff line number Diff line change
Expand Up @@ -1781,11 +1781,10 @@ request for you.
(defmodule Multipart
(hidden at?)
(private at?)
; does `s` contain `sub` starting at byte offset `pos`?
(defn at? [s pos sub]
(let [subl (String.length sub)
sl (String.length s)]
(if (or (< pos 0) (> (+ pos subl) sl))
; does `s`, whose byte length is `slen`, hold `sub` at byte offset `pos`?
(defn at? [s slen pos sub]
(let [subl (String.length sub)]
(if (or (< pos 0) (> (+ pos subl) slen))
false
(let-do [ok true
j 0]
Expand All @@ -1795,16 +1794,29 @@ request for you.
(set! ok false)))
ok))))

(hidden skip-table)
(private skip-table)
; Boyer-Moore-Horspool bad-character shifts for `needle`, indexed by byte.
(defn skip-table [needle nlen]
(let-do [t (Array.replicate 256 &nlen)
last (Int.dec nlen)]
(for [i 0 last]
(Array.aset! &t (Char.to-int (String.char-at needle i)) (- last i)))
t))

(hidden index-from)
(private index-from)
; first byte offset >= `from` where `needle` occurs in `s`, or -1.
(defn index-from [s needle from]
(let-do [slen (String.length s)
nlen (String.length needle)
res -1
i from]
; first byte offset >= `from` where `needle` occurs in `s`, or -1. `skip` is
; the needle’s `skip-table`.
(defn index-from [s slen needle nlen skip from]
(let-do [res -1
i from
last (Int.dec nlen)]
(while (and (< res 0) (<= (+ i nlen) slen))
(if (at? s i needle) (set! res i) (set! i (Int.inc i))))
(let [c (String.char-at s (+ i last))]
(if (and (= c (String.char-at needle last)) (at? s slen i needle))
(set! res i)
(set! i (+ i @(Array.unsafe-nth skip (Char.to-int c)))))))
res))

(hidden header-value)
Expand Down Expand Up @@ -1857,13 +1869,14 @@ into its `FormPart`s. Fails when the opening boundary delimiter is absent.")
cdlen (String.length &crlf-dash)
dashlen (String.length &dash)
len (String.length body)
skip (Multipart.skip-table &crlf-dash cdlen)
; the opening delimiter is `--boundary`, either at the very start or
; after a preamble that ends in CRLF; every later delimiter is
; `CRLF--boundary`, so searching for that avoids matching a bare
; `--boundary` that happens to occur inside a part body.
open (if (Multipart.at? body 0 &dash)
open (if (Multipart.at? body len 0 &dash)
dashlen
(let [p (String.index-of-string body &crlf-dash)]
(let [p (Multipart.index-from body len &crlf-dash cdlen &skip 0)]
(if (< p 0) -1 (+ p cdlen))))]
(if (< open 0)
(Result.Error @"multipart: boundary delimiter not found")
Expand All @@ -1872,11 +1885,16 @@ into its `FormPart`s. Fails when the opening boundary delimiter is absent.")
done false]
(while (not done)
(cond
(Multipart.at? body pos "--") (set! done true)
(Multipart.at? body len pos "--") (set! done true)
(>= pos len) (set! done true)
(do
(when (Multipart.at? body pos "\r\n") (set! pos (+ pos 2)))
(let [next (Multipart.index-from body &crlf-dash pos)]
(when (Multipart.at? body len pos "\r\n") (set! pos (+ pos 2)))
(let [next (Multipart.index-from body
len
&crlf-dash
cdlen
&skip
pos)]
(if (< next 0)
(set! done true)
(do
Expand Down
36 changes: 36 additions & 0 deletions test/http.carp
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,42 @@
(Result.Error _) true
(Result.Success _) false)
"a missing boundary delimiter is an error")
(assert-equal test
&(continuation 64)
&(mp-body
&(mp-parts
&(fmt
"--b\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\n%s\r\n--b--\r\n"
&(continuation 64))
"b")
0)
"a part body that is not valid UTF-8 round-trips")
(assert-equal test
"ok"
&(mp-body
&(mp-parts
&(fmt
"%s\r\n--b\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nok\r\n--b--\r\n"
&(continuation 64))
"b")
0)
"a preamble that is not valid UTF-8 is ignored")
(assert-equal test
"888--LongBoundary8"
&(mp-body
&(mp-parts
"--LongBoundary8\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\n888--LongBoundary8\r\n--LongBoundary8--\r\n"
"LongBoundary8")
0)
"a body repeating the delimiter’s last byte is scanned correctly")
(assert-equal test
"hello"
&(mp-body
&(mp-parts
"--b\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nhello\r\n--b"
"b")
0)
"a delimiter that ends at the end of the body is still found")

; ---- Request.multipart-data ----
(assert-equal test
Expand Down