Artifact [42f61a7166]

Artifact 42f61a7166496b87fac8ebb1440755daa3a8caf5:


defmodule Plug.Adapters.Test.Conn do
  @behaviour Plug.Conn.Adapter
  @moduledoc false

  ## Test helpers

  def conn(conn, method, uri, body_or_params) do
    maybe_flush()

    uri    = URI.parse(uri)
    method = method |> to_string |> String.upcase
    query  = uri.query || ""
    owner  = self()

    {body, params, req_headers} = body_or_params(body_or_params, query, conn.req_headers)
    state = %{method: method, params: params, req_body: body,
              chunks: nil, ref: make_ref, owner: owner}

    %Plug.Conn{conn |
      adapter: {__MODULE__, state},
      host: uri.host || "www.example.com",
      method: method,
      owner: owner,
      path_info: split_path(uri.path),
      port: uri.port || 80,
      peer: {{127, 0, 0, 1}, 111317},
      remote_ip: {127, 0, 0, 1},
      req_headers: req_headers,
      request_path: uri.path,
      query_string: query,
      params: params || %Plug.Conn.Unfetched{aspect: :params},
      scheme: (uri.scheme || "http") |> String.downcase |> String.to_atom}
  end

  ## Connection adapter

  def send_resp(%{method: "HEAD"} = state, status, headers, _body) do
    do_send state, status, headers, ""
  end
  def send_resp(state, status, headers, body) do
    do_send state, status, headers, IO.iodata_to_binary(body)
  end

  def send_file(%{method: "HEAD"} = state, status, headers, _path, _offset, _length) do
    do_send state, status, headers, ""
  end
  def send_file(state, status, headers, path, offset, length) do
    %File.Stat{type: :regular, size: size} = File.stat!(path)

    length =
      cond do
        length == :all -> size
        is_integer(length) -> length
      end

    {:ok, data} = File.open!(path, [:read, :binary], fn device ->
      :file.pread(device, offset, length)
    end)

    do_send state, status, headers, data
  end

  def send_chunked(state, _status, _headers),
    do: {:ok, "", %{state | chunks: ""}}
  def chunk(%{method: "HEAD"} = state, _body),
    do: {:ok, "", state}
  def chunk(%{chunks: chunks} = state, body) do
    body = chunks <> IO.iodata_to_binary(body)
    {:ok, body, %{state | chunks: body}}
  end

  defp do_send(%{owner: owner, ref: ref} = state, status, headers, body) do
    send owner, {ref, {status, headers, body}}
    {:ok, body, state}
  end

  def read_req_body(%{req_body: body} = state, opts \\ []) do
    size = min(byte_size(body), Keyword.get(opts, :length, 8_000_000))
    data = :binary.part(body, 0, size)
    rest = :binary.part(body, size, byte_size(body) - size)
    tag =
      case rest do
        "" -> :ok
        _  -> :more
      end
    {tag, data, %{state | req_body: rest}}
  end

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

  ## Private helpers

  defp body_or_params(nil, _query, headers),
    do: {"", nil, headers}

  defp body_or_params(body, _query, headers) when is_binary(body) do
    {body, nil, headers}
  end

  defp body_or_params(params, query, headers) when is_list(params) do
    body_or_params(Enum.into(params, %{}), query, headers)
  end

  defp body_or_params(params, query, headers) when is_map(params) do
    headers = :lists.keystore("content-type", 1, headers,
                              {"content-type", "multipart/mixed; charset: utf-8"})
    params = Map.merge(Plug.Conn.Query.decode(query), stringify_params(params))
    {"", params, headers}
  end

  defp stringify_params([{_, _}|_] = params),
    do: Enum.into(params, %{}, &stringify_kv/1)
  defp stringify_params([_|_] = params),
    do: Enum.map(params, &stringify_params/1)
  defp stringify_params(%{__struct__: mod} = struct) when is_atom(mod),
    do: struct
  defp stringify_params(%{} = params),
    do: Enum.into(params, %{}, &stringify_kv/1)
  defp stringify_params(other),
    do: other

  defp stringify_kv({k, v}),
    do: {to_string(k), stringify_params(v)}

  defp split_path(path) do
    segments = :binary.split(path, "/", [:global])
    for segment <- segments, segment != "", do: segment
  end

  @already_sent {:plug_conn, :sent}

  defp maybe_flush() do
    receive do
      @already_sent -> :ok
    after
      0 -> :ok
    end
  end
end