Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113609
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/tesla/adapter/httpc.ex b/lib/tesla/adapter/httpc.ex
index d910af0..343be81 100644
--- a/lib/tesla/adapter/httpc.ex
+++ b/lib/tesla/adapter/httpc.ex
@@ -1,94 +1,97 @@
defmodule Tesla.Adapter.Httpc do
@moduledoc """
Adapter for [httpc](http://erlang.org/doc/man/httpc.html)
This is the default adapter.
**NOTE** Tesla overrides default autoredirect value with false to ensure
consistency between adapters
"""
@behaviour Tesla.Adapter
import Tesla.Adapter.Shared, only: [stream_to_fun: 1, next_chunk: 1]
alias Tesla.Multipart
@override_defaults autoredirect: false
@http_opts ~w(timeout connect_timeout ssl essl autoredirect proxy_auth version relaxed url_encode)a
def call(env, opts) do
opts = Tesla.Adapter.opts(@override_defaults, env, opts)
with {:ok, {status, headers, body}} <- request(env, opts) do
{:ok, format_response(env, status, headers, body)}
end
end
defp format_response(env, {_, status, _}, headers, body) do
%{env | status: status, headers: format_headers(headers), body: format_body(body)}
end
# from http://erlang.org/doc/man/httpc.html
# headers() = [header()]
# header() = {field(), value()}
# field() = string()
# value() = string()
defp format_headers(headers) do
for {key, value} <- headers do
{String.downcase(to_string(key)), to_string(value)}
end
end
# from http://erlang.org/doc/man/httpc.html
# string() = list of ASCII characters
# Body = string() | binary()
defp format_body(data) when is_list(data), do: IO.iodata_to_binary(data)
defp format_body(data) when is_binary(data), do: data
defp request(env, opts) do
content_type = to_charlist(Tesla.get_header(env, "content-type") || "")
handle(
request(
env.method,
Tesla.build_url(env.url, env.query) |> to_charlist,
Enum.map(env.headers, fn {k, v} -> {to_charlist(k), to_charlist(v)} end),
content_type,
env.body,
Keyword.split(opts, @http_opts)
)
)
end
defp request(method, url, headers, _content_type, nil, {http_opts, opts}) do
:httpc.request(method, {url, headers}, http_opts, opts)
end
defp request(method, url, headers, _content_type, %Multipart{} = mp, opts) do
headers = headers ++ Multipart.headers(mp)
headers = for {key, value} <- headers, do: {to_charlist(key), to_charlist(value)}
- {content_type, headers} = case List.keytake(headers, 'content-type', 0) do
- nil -> {'text/plain', headers}
- {{_, ct}, headers} -> {ct, headers}
- end
+
+ {content_type, headers} =
+ case List.keytake(headers, 'content-type', 0) do
+ nil -> {'text/plain', headers}
+ {{_, ct}, headers} -> {ct, headers}
+ end
+
body = stream_to_fun(Multipart.body(mp))
request(method, url, headers, to_charlist(content_type), body, opts)
end
defp request(method, url, headers, content_type, %Stream{} = body, opts) do
fun = stream_to_fun(body)
request(method, url, headers, content_type, fun, opts)
end
defp request(method, url, headers, content_type, body, opts) when is_function(body) do
body = {:chunkify, &next_chunk/1, body}
request(method, url, headers, content_type, body, opts)
end
defp request(method, url, headers, content_type, body, {http_opts, opts}) do
:httpc.request(method, {url, headers, content_type, body}, http_opts, opts)
end
defp handle({:error, {:failed_connect, _}}), do: {:error, :econnrefused}
defp handle(response), do: response
end
diff --git a/lib/tesla/middleware/digest_auth.ex b/lib/tesla/middleware/digest_auth.ex
index 283630a..8dad848 100644
--- a/lib/tesla/middleware/digest_auth.ex
+++ b/lib/tesla/middleware/digest_auth.ex
@@ -1,131 +1,131 @@
defmodule Tesla.Middleware.DigestAuth do
@behaviour Tesla.Middleware
@moduledoc """
Digest access authentication middleware
[Wiki on the topic](https://en.wikipedia.org/wiki/Digest_access_authentication)
**NOTE**: Currently the implementation is incomplete and works only for MD5 algorithm
and auth qop.
### Example
```
defmodule MyClient do
use Tesla
def client(username, password, opts \\ %{}) do
Tesla.build_client [
{Tesla.Middleware.DigestAuth, Map.merge(%{username: username, password: password}, opts)}
]
end
end
```
### Options
- `:username` - username (defaults to `""`)
- `:password` - password (defaults to `""`)
- `:cnonce_fn` - custom function generating client nonce (defaults to `&Tesla.Middleware.DigestAuth.cnonce/0`)
- `:nc` - nonce counter (defaults to `"00000000"`)
"""
def call(env, next, opts) do
if env.opts && Keyword.get(env.opts, :digest_auth_handshake) do
Tesla.run(env, next)
else
opts = opts || %{}
with {:ok, headers} <- authorization_header(env, opts) do
env
|> Tesla.put_headers(headers)
|> Tesla.run(next)
end
end
end
defp authorization_header(env, opts) do
with {:ok, vars} <- authorization_vars(env, opts) do
{:ok,
vars
|> calculated_authorization_values
|> create_header}
end
end
defp authorization_vars(env, opts) do
with {:ok, unauthorized_response} <-
env.__module__.get(
env.__client__,
env.url,
opts: Keyword.put(env.opts || [], :digest_auth_handshake, true)
) do
{:ok,
%{
username: opts[:username] || "",
password: opts[:password] || "",
path: URI.parse(env.url).path,
auth:
Tesla.get_header(unauthorized_response, "www-authenticate")
|> parse_www_authenticate_header,
method: env.method |> to_string |> String.upcase(),
- client_nonce: (opts[:cnonce_fn] || &cnonce/0).(),
+ client_nonce: (opts[:cnonce_fn] || (&cnonce/0)).(),
nc: opts[:nc] || "00000000"
}}
end
end
defp calculated_authorization_values(%{auth: auth}) when auth == %{}, do: []
defp calculated_authorization_values(auth_vars) do
[
{"username", auth_vars.username},
{"realm", auth_vars.auth["realm"]},
{"uri", auth_vars[:path]},
{"nonce", auth_vars.auth["nonce"]},
{"nc", auth_vars.nc},
{"cnonce", auth_vars.client_nonce},
{"response", response(auth_vars)},
# hard-coded, will not work for MD5-sess
{"algorithm", "MD5"},
# hard-coded, will not work for auth-int or unspecified
{"qop", "auth"}
]
end
defp single_header_val({k, v}) when k in ~w(nc qop algorithm), do: "#{k}=#{v}"
defp single_header_val({k, v}), do: "#{k}=\"#{v}\""
defp create_header([]), do: []
defp create_header(calculated_authorization_values) do
vals =
calculated_authorization_values
|> Enum.reduce([], fn val, acc -> [single_header_val(val) | acc] end)
|> Enum.join(", ")
[{"authorization", "Digest #{vals}"}]
end
defp ha1(%{username: username, auth: %{"realm" => realm}, password: password}) do
md5("#{username}:#{realm}:#{password}")
end
defp ha2(%{method: method, path: path}) do
md5("#{method}:#{path}")
end
defp response(%{auth: %{"nonce" => nonce}, nc: nc, client_nonce: client_nonce} = auth_vars) do
md5("#{ha1(auth_vars)}:#{nonce}:#{nc}:#{client_nonce}:auth:#{ha2(auth_vars)}")
end
defp parse_www_authenticate_header(nil), do: %{}
defp parse_www_authenticate_header(header) do
Regex.scan(~r/(\w+?)="(.+?)"/, header)
|> Enum.reduce(%{}, fn [_, key, val], acc -> Map.merge(acc, %{key => val}) end)
end
defp md5(data), do: Base.encode16(:erlang.md5(data), case: :lower)
defp cnonce, do: :crypto.strong_rand_bytes(4) |> Base.encode16(case: :lower)
end
diff --git a/lib/tesla/migration.ex b/lib/tesla/migration.ex
index 213efe2..cfea904 100644
--- a/lib/tesla/migration.ex
+++ b/lib/tesla/migration.ex
@@ -1,111 +1,113 @@
defmodule Tesla.Migration do
## ALIASES
@breaking_alias "https://github.com/teamon/tesla/wiki/0.x-to-1.0-Migration-Guide#dropped-aliases-support-159"
@breaking_headers_map "https://github.com/teamon/tesla/wiki/0.x-to-1.0-Migration-Guide#headers-are-now-a-list-160"
@breaking_client_fun "https://github.com/teamon/tesla/wiki/0.x-to-1.0-Migration-Guide#dropped-client-as-function-176"
def breaking_alias!(_kind, _name, nil), do: nil
def breaking_alias!(kind, name, caller) do
raise CompileError,
file: caller.file,
line: caller.line,
description: """
#{kind |> to_string |> String.capitalize()} aliases and local functions has been removed.
- Use full #{kind} name or define a middleware module #{name |> to_string() |> String.capitalize()}
+ Use full #{kind} name or define a middleware module #{
+ name |> to_string() |> String.capitalize()
+ }
#{snippet(caller)}
See #{@breaking_alias}
"""
end
def breaking_alias_in_config!(module) do
check_config(
Application.get_env(:tesla, module, [])[:adapter],
"config :tesla, #{inspect(module)}, adapter: "
)
check_config(Application.get_env(:tesla, :adapter), "config :tesla, adapter: ")
end
defp check_config(nil, _label), do: nil
defp check_config({module, _opts}, label) do
check_config(module, label)
end
defp check_config(module, label) do
unless elixir_module?(module) do
raise CompileError,
description: """
Calling
#{label}#{inspect(module)}
with atom as argument has been deprecated
Use
#{label}Tesla.Adapter.Name
instead
See #{@breaking_alias}
"""
end
end
## HEADERS AS LIST
def breaking_headers_map!(
{:__aliases__, _, [:Tesla, :Middleware, :Headers]},
{:%{}, _, _},
caller
) do
raise CompileError,
file: caller.file,
line: caller.line,
description: """
Headers are now a list instead of a map.
#{snippet(caller)}
See #{@breaking_headers_map}
"""
end
def breaking_headers_map!(_middleware, _opts, _caller), do: nil
## CLIENT FUNCTION
def client_function! do
raise RuntimeError,
message: """
Using anonymous function as client has been removed.
Use `Tesla.build_client` instead
See #{@breaking_client_fun}
"""
end
## UTILS
defp elixir_module?(atom) do
atom |> Atom.to_string() |> String.starts_with?("Elixir.")
end
defp snippet(caller) do
caller.file
|> File.read!()
|> String.split("\n")
|> Enum.at(caller.line - 1)
rescue
_ in File.Error -> ""
end
end
diff --git a/test/tesla/0.x_to_1.0_migration_test.exs b/test/tesla/0.x_to_1.0_migration_test.exs
index ce43240..36ceaf7 100644
--- a/test/tesla/0.x_to_1.0_migration_test.exs
+++ b/test/tesla/0.x_to_1.0_migration_test.exs
@@ -1,121 +1,122 @@
defmodule MigrationTest do
use ExUnit.Case
describe "Drop aliases #159" do
test "compile error when using atom as plug" do
assert_raise CompileError, fn ->
Code.compile_quoted(
quote do
defmodule Client1 do
use Tesla
plug :json
end
end
)
end
end
test "compile error when using atom as plug even if there is a local function with that name" do
assert_raise CompileError, fn ->
Code.compile_quoted(
quote do
defmodule Client2 do
use Tesla
plug :json
def json(env, next), do: Tesla.run(env, next)
end
end
)
end
end
test "compile error when using atom as adapter" do
assert_raise CompileError, fn ->
Code.compile_quoted(
quote do
defmodule Client3 do
use Tesla
adapter :hackney
end
end
)
end
end
test "compile error when using atom as adapter with opts" do
assert_raise CompileError, fn ->
Code.compile_quoted(
quote do
defmodule Client4 do
use Tesla
adapter :hackney, recv_timeout: 10_000
end
end
)
end
end
test "compile error when using atom as adapter even if there is a local function with that name" do
assert_raise CompileError, fn ->
Code.compile_quoted(
quote do
defmodule Client5 do
use Tesla
adapter :local
def local(env), do: env
end
end
)
end
end
test "compile error when using atom as adapter in config" do
assert_raise CompileError, fn ->
Application.put_env(:tesla, Client6, adapter: :mock)
Code.compile_quoted(
quote do
defmodule Client6 do
use Tesla
end
end
)
end
end
end
describe "Use keyword list to store headers #160" do
test "compile error when passing a map to Headers middleware" do
assert_raise CompileError, fn ->
Code.compile_quoted(
quote do
defmodule Client7 do
use Tesla
plug Tesla.Middleware.Headers, %{"User-Agent" => "tesla"}
end
end
)
end
end
test "no error when passing a list to Headers middleware" do
Code.compile_quoted(
quote do
defmodule Client8 do
use Tesla
plug Tesla.Middleware.Headers, [{"user-agent", "tesla"}]
end
end
)
end
end
describe "Drop client as function #176" do
test "error when passing a function as client" do
client = fn env, next -> Tesla.run(env, next) end
+
assert_raise RuntimeError, fn ->
Tesla.get(client, "/")
end
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 12:33 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39794
Default Alt Text
(14 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment