Skip to content

Commit

Permalink
Implementation of multipart parsing for test adapter (#501)
Browse files Browse the repository at this point in the history
  • Loading branch information
leifg authored and josevalim committed Jun 6, 2017
1 parent 99ac580 commit 142ec1a
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 9 deletions.
35 changes: 33 additions & 2 deletions lib/plug/adapters/test/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,16 @@ defmodule Plug.Adapters.Test.Conn do
{tag, data, %{state | req_body: rest}}
end

def parse_req_multipart(%{params: multipart} = state, _limit, _callback) do
{:ok, multipart, %{state | params: nil}}
def parse_req_multipart(%{params: params} = state, _opts, _callback) do
{:ok, params, state}
end

def parse_req_multipart(%{req_body: multipart} = state, opts, callback) do
boundary = Keyword.get(opts, :boundary)
params = parse_multipart(:cow_multipart.parse_headers(multipart, boundary), boundary, [], callback)
|> Enum.reduce(%{}, &Plug.Conn.Query.decode_pair/2)

{:ok, params, state}
end

## Private helpers
Expand Down Expand Up @@ -138,4 +146,27 @@ defmodule Plug.Adapters.Test.Conn do
0 -> :ok
end
end

defp parse_multipart({:ok, headers, body}, boundary, acc, callback) do
{:done, content, rest} = :cow_multipart.parse_body(body, boundary)

case callback.(headers)do
{:file, name, path, %Plug.Upload{} = uploaded} ->
{:ok, file} = File.open(path, [:write, :binary, :delayed_write, :raw])
IO.binwrite(file, content)
File.close(file)

parse_multipart(:cow_multipart.parse_headers(rest, boundary), boundary, [{name, uploaded}|acc], callback)

{:binary, name} ->
parse_multipart(:cow_multipart.parse_headers(rest, boundary), boundary, [{name, content}|acc], callback)

:skip ->
parse_multipart(:cow_multipart.parse_headers(rest, boundary), boundary, acc, callback)
end
end

defp parse_multipart({:done, _rest}, _boundary, acc, _callback) do
acc
end
end
11 changes: 8 additions & 3 deletions lib/plug/parsers/multipart.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ defmodule Plug.Parsers.MULTIPART do

@behaviour Plug.Parsers

def parse(conn, "multipart", subtype, _headers, opts) when subtype in ["form-data", "mixed"] do
def parse(conn, "multipart", subtype, headers, opts) when subtype in ["form-data", "mixed"] do
{adapter, state} = conn.adapter

try do
adapter.parse_req_multipart(state, opts, &handle_headers/1)
adapter.parse_req_multipart(state, add_boundary(opts, Map.get(headers, "boundary")), &handle_headers/1)
rescue
e in Plug.UploadError -> # Do not ignore upload errors
reraise e, System.stacktrace
Expand All @@ -36,7 +36,7 @@ defmodule Plug.Parsers.MULTIPART do
{:next, conn}
end

defp handle_headers(headers) do
def handle_headers(headers) do
case List.keyfind(headers, "content-disposition", 0) do
{_, disposition} -> handle_disposition(disposition, headers)
nil -> :skip
Expand Down Expand Up @@ -74,4 +74,9 @@ defmodule Plug.Parsers.MULTIPART do
nil -> nil
end
end

defp add_boundary(opts, nil), do: opts
defp add_boundary(opts, boundary) do
opts ++ [{:boundary, boundary}]
end
end
50 changes: 46 additions & 4 deletions test/plug/adapters/test/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,52 @@ defmodule Plug.Adapters.Test.ConnTest do
end

test "parse_req_multipart/4" do
conn = conn(:get, "/", a: "b", c: [%{d: "e"}, "f"])
{adapter, state} = conn.adapter
assert {:ok, params, _} = adapter.parse_req_multipart(state, 1_000_000, fn _ -> :ok end)
assert params == %{"a" => "b", "c" => [%{"d" => "e"}, "f"]}
multipart = """
------w58EW1cEpjzydSCq\r
Content-Disposition: form-data; name=\"name\"\r
\r
hello\r
------w58EW1cEpjzydSCq\r
Content-Disposition: form-data; name=\"pic\"; filename=\"foo.txt\"\r
Content-Type: text/plain\r
\r
hello
\r
------w58EW1cEpjzydSCq\r
Content-Disposition: form-data; name=\"empty\"; filename=\"\"\r
Content-Type: application/octet-stream\r
\r
\r
------w58EW1cEpjzydSCq\r
Content-Disposition: form-data; name="status[]"\r
\r
choice1\r
------w58EW1cEpjzydSCq\r
Content-Disposition: form-data; name="status[]"\r
\r
choice2\r
------w58EW1cEpjzydSCq\r
Content-Disposition: form-data; name=\"commit\"\r
\r
Create User\r
------w58EW1cEpjzydSCq--\r
"""

conn = conn(:post, "/")

{adapter, _state} = conn.adapter

assert {:ok, params, _} = adapter.parse_req_multipart(%{req_body: multipart}, [{:boundary, "----w58EW1cEpjzydSCq"}], &Plug.Parsers.MULTIPART.handle_headers/1)

assert params["name"] == "hello"
assert params["status"] == ["choice1", "choice2"]
assert params["empty"] == nil

assert %Plug.Upload{} = file = params["pic"]
assert File.read!(file.path) == "hello\n\n"
assert file.content_type == "text/plain"
assert file.filename == "foo.txt"
end

test "use existing conn.host if exists" do
Expand Down

0 comments on commit 142ec1a

Please sign in to comment.