Skip to content

Commit

Permalink
Form parsing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lpil committed Aug 10, 2023
1 parent 28259b2 commit 96a0214
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 14 deletions.
29 changes: 15 additions & 14 deletions src/wisp.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -647,15 +647,6 @@ fn read_body_loop(
}
}

// TODO: test - urlencoded
// TODO: test - urlencoded body invalid
// TODO: test - multipart
// TODO: test - multipart no boundary
// TODO: test - multipart body invalid
// TODO: test - files
// TODO: test - body too big
// TODO: test - files too big
// TODO: test - unknown content type
/// A middleware which extracts form data from the body of a request that is
/// encoded as either `application/x-www-form-urlencoded` or
/// `multipart/form-data`.
Expand Down Expand Up @@ -698,6 +689,8 @@ pub fn require_form(
Ok("multipart/form-data; boundary=" <> boundary) ->
require_multipart_form(request, boundary, next)

Ok("multipart/form-data") -> bad_request()

_ -> unsupported_media_type()
}
}
Expand Down Expand Up @@ -821,19 +814,27 @@ fn multipart_body(
data: t,
) -> Result(#(Option(BufferedReader), Int, t), Response) {
use #(chunk, reader) <- result.try(read_chunk(reader, chunk_size))
let size_read = bit_string.byte_size(chunk)
use output <- result.try(parse(chunk))

case output {
http.MultipartBody(chunk, done, remaining) -> {
let used = bit_string.byte_size(chunk) - bit_string.byte_size(remaining)
use quotas <- result.try(decrement_quota(quota, used))
http.MultipartBody(parsed, done, remaining) -> {
// Decrement the quota by the number of bytes consumed.
let used = size_read - bit_string.byte_size(remaining) - 2
let used = case done {
// If this is the last chunk, we need to account for the boundary.
True -> used - 4 - string.byte_size(boundary)
False -> used
}
use quota <- result.try(decrement_quota(quota, used))

let reader = BufferedReader(reader, remaining)
let reader = case done {
True -> option.None
False -> option.Some(reader)
}
use value <- result.map(append(data, chunk))
#(reader, quotas, value)
use value <- result.map(append(data, parsed))
#(reader, quota, value)
}

http.MoreRequiredForBody(chunk, parse) -> {
Expand Down
173 changes: 173 additions & 0 deletions test/wisp_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ pub fn main() {
gleeunit.main()
}

fn form_handler(
request: wisp.Request,
callback: fn(wisp.FormData) -> anything,
) -> wisp.Response {
use form <- wisp.require_form(request)
callback(form)
wisp.ok()
}

pub fn ok_test() {
wisp.ok()
|> should.equal(Response(200, [], wisp.Empty))
Expand Down Expand Up @@ -390,3 +399,167 @@ pub fn temporary_file_test() {
let assert Error(simplifile.Enoent) = simplifile.read(request2_file1)
let assert Error(simplifile.Enoent) = simplifile.read(request2_file2)
}

pub fn urlencoded_form_test() {
<<"one=1&two=2":utf8>>
|> wisp.test_request
|> request.set_header("content-type", "application/x-www-form-urlencoded")
|> form_handler(fn(form) {
form
|> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], []))
})
|> should.equal(wisp.ok())
}

pub fn urlencoded_too_big_form_test() {
<<"12":utf8>>
|> wisp.test_request
|> request.set_header("content-type", "application/x-www-form-urlencoded")
|> wisp.set_max_body_size(1)
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(413, [], wisp.Empty))
}

pub fn multipart_form_test() {
<<
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"\r
\r
2\r
--theboundary--\r
":utf8,
>>
|> wisp.test_request
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(form) {
form
|> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], []))
})
|> should.equal(wisp.ok())
}

pub fn multipart_form_too_big_test() {
<<
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary--\r
":utf8,
>>
|> wisp.test_request
|> wisp.set_max_body_size(1)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(413, [], wisp.Empty))
}

pub fn multipart_form_no_boundary_test() {
<<
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary--\r
":utf8,
>>
|> wisp.test_request
|> request.set_header("content-type", "multipart/form-data")
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(400, [], wisp.Empty))
}

pub fn multipart_form_invalid_format_test() {
<<"--theboundary\r\n--theboundary--\r\n":utf8>>
|> wisp.test_request
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(400, [], wisp.Empty))
}

pub fn form_unknown_content_type_test() {
<<"one=1&two=2":utf8>>
|> wisp.test_request
|> request.set_header("content-type", "text/form")
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(415, [], wisp.Empty))
}

pub fn multipart_form_with_files_test() {
<<
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"; filename=\"file.txt\"\r
\r
file contents\r
--theboundary--\r
":utf8,
>>
|> wisp.test_request
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(form) {
let assert [#("one", "1")] = form.values
let assert [#("two", wisp.UploadedFile("file.txt", path))] = form.files
let assert Ok("file contents") = simplifile.read(path)
})
|> should.equal(wisp.ok())
}

pub fn multipart_form_files_too_big_test() {
let test = fn(limit, callback) {
<<
"--theboundary\r
Content-Disposition: form-data; name=\"two\"; filename=\"file.txt\"\r
\r
12\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"\r
\r
this one isn't a file. If it was it would use the entire quota.\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"; filename=\"another.txt\"\r
\r
34\r
--theboundary--\r
":utf8,
>>
|> wisp.test_request
|> wisp.set_max_files_size(limit)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(callback)
}

test(1, fn(_) { panic as "should be unreachable for limit of 1" })
|> should.equal(Response(413, [], wisp.Empty))

test(2, fn(_) { panic as "should be unreachable for limit of 2" })
|> should.equal(Response(413, [], wisp.Empty))

test(3, fn(_) { panic as "should be unreachable for limit of 3" })
|> should.equal(Response(413, [], wisp.Empty))

test(4, fn(_) { Nil })
|> should.equal(Response(200, [], wisp.Empty))
}

0 comments on commit 96a0214

Please sign in to comment.