diff --git a/lib/sugar/request/https_only.ex b/lib/sugar/request/https_only.ex
new file mode 100644
index 0000000..52d473c
--- /dev/null
+++ b/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
\ No newline at end of file
diff --git a/lib/sugar/request/parsers/xml.ex b/lib/sugar/request/parsers/xml.ex
new file mode 100644
index 0000000..462d796
--- /dev/null
+++ b/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
\ No newline at end of file
diff --git a/lib/sugar/router.ex b/lib/sugar/router.ex
index 6609c4c..645103e 100644
--- a/lib/sugar/router.ex
+++ b/lib/sugar/router.ex
@@ -79,6 +79,7 @@ defmodule Sugar.Router do
:urlencoded,
:multipart ],
json_decoder: JSEX
+ plug :copy_req_content_type
end
end
@@ -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
@@ -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
diff --git a/test/sugar/request/https_only_test.exs b/test/sugar/request/https_only_test.exs
new file mode 100644
index 0000000..e4ddfff
--- /dev/null
+++ b/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
\ No newline at end of file
diff --git a/test/sugar/request/parsers_test.exs b/test/sugar/request/parsers_test.exs
new file mode 100644
index 0000000..6a6ea44
--- /dev/null
+++ b/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", "baz", 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", "", 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", "", 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", "baz", headers: headers), length: 5)
+ end
+
+ assert Plug.Exception.status(exception) === 413
+ end
+end
\ No newline at end of file