Skip to content

Commit

Permalink
round three
Browse files Browse the repository at this point in the history
fix copy_req_content_type router plug.
add HttpsOnly and XML parser
  • Loading branch information
slogsdon committed Dec 24, 2014
1 parent 29d76da commit 4d57f29
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 4 deletions.
10 changes: 10 additions & 0 deletions lib/sugar/request/https_only.ex
@@ -0,0 +1,10 @@
defmodule Sugar.Request.HttpsOnly do
@moduledoc false
import Plug.Conn

def init(opts), do: opts

def call(conn, _opts) do
conn |> send_resp(403, "Forbidden")
end
end
65 changes: 65 additions & 0 deletions lib/sugar/request/parsers/xml.ex
@@ -0,0 +1,65 @@
defmodule Sugar.Request.Parsers.XML do
@moduledoc false
alias Plug.Conn

@types [ "application", "text" ]

@type conn :: map
@type headers :: map
@type opts :: Keyword.t

@spec parse(conn, binary, binary, headers, opts) :: {:ok | :error, map | atom, conn}
def parse(%Conn{} = conn, type, "xml", _headers, opts) when type in @types do
case Conn.read_body(conn, opts) do
{ :ok, body, conn } ->
{ :ok, %{ xml: body |> do_parse }, conn }
{ :more, _data, conn } ->
{ :error, :too_large, conn }
end
end
def parse(conn, _type, _subtype, _headers, _opts) do
{ :next, conn }
end

defp do_parse(xml) do
:erlang.bitstring_to_list(xml)
|> :xmerl_scan.string
|> elem(0)
|> do_parse_nodes
end

defp do_parse_nodes([]), do: []
defp do_parse_nodes([ h | t ]) do
do_parse_nodes(h) ++ do_parse_nodes(t)
end
defp do_parse_nodes({ :xmlAttribute, name, _, _, _, _, _, _, value, _ }) do
[ { name, value |> to_string } ]
end
defp do_parse_nodes({ :xmlElement, name, _, _, _, _, _, attrs, els, _, _, _ }) do
value = els
|> do_parse_nodes
|> flatten
[ %{ name: name,
attr: attrs |> do_parse_nodes,
value: value } ]
end
defp do_parse_nodes({ :xmlText, _, _, _, value, _ }) do
string_value = value
|> to_string
|> String.strip
if string_value |> String.length > 0 do
[ string_value ]
else
[]
end
end

defp flatten([]), do: []
defp flatten(values) do
if Enum.all?(values, &(is_binary(&1))) do
[ values |> List.to_string ]
else
values
end
end
end
8 changes: 4 additions & 4 deletions lib/sugar/router.ex
Expand Up @@ -79,6 +79,7 @@ defmodule Sugar.Router do
:urlencoded,
:multipart ],
json_decoder: JSEX
plug :copy_req_content_type
end
end

Expand All @@ -87,11 +88,10 @@ defmodule Sugar.Router do
# Plugs we want predefined but aren't necessary to be before
# user-defined plugs
defaults = [ { Plug.Head, [], true },
{ Plug.MethodOverride, [], true },
{ :copy_req_content_type, [], true },
{ Plug.MethodOverride, [], true },
{ :match, [], true },
{ :dispatch, [], true } ]
{ conn, body } = Enum.reverse(defaults) ++
{ conn, body } = Enum.reverse(defaults) ++
Module.get_attribute(env.module, :plugs)
|> Plug.Builder.compile

Expand All @@ -107,7 +107,7 @@ defmodule Sugar.Router do
defoverridable [init: 1, call: 2]

def copy_req_content_type(conn, _opts) do
default = Application.get_env(:sugar, :default_content_type, "application/json; charset=utf-8")
default = Application.get_env(:sugar, :default_content_type, "text/html; charset=utf-8")
content_type = case Plug.Conn.get_req_header conn, "content-type" do
[content_type] -> content_type
_ -> default
Expand Down
13 changes: 13 additions & 0 deletions test/sugar/request/https_only_test.exs
@@ -0,0 +1,13 @@
defmodule Sugar.Request.HttpsOnlyTest do
use ExUnit.Case, async: true
import Plug.Test

test "translates json extension" do
opts = Sugar.Request.HttpsOnly.init([])
conn = conn(:get, "/get.json")
|> Sugar.Request.HttpsOnly.call(opts)

assert conn.status === 403
assert conn.resp_body === "Forbidden"
end
end
56 changes: 56 additions & 0 deletions test/sugar/request/parsers_test.exs
@@ -0,0 +1,56 @@
defmodule Sugar.Request.ParsersTest do
use ExUnit.Case, async: true
import Plug.Test

@parsers [ Sugar.Request.Parsers.XML ]

def parse(conn, opts \\ []) do
opts = Keyword.put_new(opts, :parsers, @parsers)
Plug.Parsers.call(conn, Plug.Parsers.init(opts))
end

test "parses xml encoded bodies" do
headers = [{"content-type", "application/xml"}]
conn = parse(conn(:post, "/post", "<foo>baz</foo>", headers: headers))
foo = conn.params.xml
|> Enum.find(fn node ->
node.name === :foo
end)

assert foo.value |> hd === "baz"
end

test "parses xml encoded bodies with xml nodes" do
headers = [{"content-type", "application/xml"}]
conn = parse(conn(:post, "/post", "<foo><bar/><baz/></foo>", headers: headers))
foo = conn.params.xml
|> Enum.find(fn node ->
node.name === :foo
end)
bar = foo.value |> hd

assert foo.value |> Enum.count === 2
assert bar.name === :bar
end

test "parses xml encoded bodies with attributes" do
headers = [{"content-type", "application/xml"}]
conn = parse(conn(:post, "/post", "<foo bar=\"baz\" id=\"1\" />", headers: headers))
foo = conn.params.xml
|> Enum.find(fn node ->
node.name === :foo
end)

assert foo.attr[:bar] === "baz"
assert foo.attr[:id] === "1"
end

test "xml parser errors when body too large" do
exception = assert_raise Plug.Parsers.RequestTooLargeError, fn ->
headers = [{"content-type", "application/xml"}]
parse(conn(:post, "/post", "<foo>baz</foo>", headers: headers), length: 5)
end

assert Plug.Exception.status(exception) === 413
end
end

0 comments on commit 4d57f29

Please sign in to comment.