Skip to content

Commit

Permalink
Merge pull request derekkraan#26 from derekkraan/feature/proxy
Browse files Browse the repository at this point in the history
Proxy Flags
  • Loading branch information
kevinschweikert authored Dec 22, 2024
2 parents 5654376 + 5846656 commit 68e4549
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.98.7

- Add new supported flags: `--proxy` and `--proxy-user`

## 0.98.6
- Handle `--data-raw` and `--data-ascii` ([#16](https://github.com/derekkraan/curl_req/pull/16))
- Strip `$` as necessary
Expand Down
28 changes: 28 additions & 0 deletions lib/curl_req.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ defmodule CurlReq do
* `-I`/`--head`
* `-d`/`--data`/`--data-ascii`
* `--data-raw`
* `-x`/`--proxy`
* `-U`/`--proxy-user`
Options:
Expand Down Expand Up @@ -127,6 +129,24 @@ defmodule CurlReq do
%{auth: {:basic, credentials}} ->
[user_flag(flag_style), credentials] ++ [basic_auth_flag()]

%{connect_options: connect_options} ->
proxy =
case Keyword.get(connect_options, :proxy) do
nil ->
[]

{scheme, host, port, _} ->
[proxy_flag(flag_style), "#{scheme}://#{host}:#{port}"]
end

case Keyword.get(connect_options, :proxy_headers) do
[{"proxy-authorization", "Basic " <> encoded_creds}] ->
proxy ++ [proxy_user_flag(flag_style), Base.decode64!(encoded_creds)]

_ ->
proxy
end

_ ->
[]
end
Expand Down Expand Up @@ -188,6 +208,12 @@ defmodule CurlReq do

defp compressed_flag(), do: "--compressed"

defp proxy_flag(:short), do: "-x"
defp proxy_flag(:long), do: "--proxy"

defp proxy_user_flag(:short), do: "-U"
defp proxy_user_flag(:long), do: "--proxy-user"

@doc """
Transforms a curl command into a Req request.
Expand All @@ -201,6 +227,8 @@ defmodule CurlReq do
* `-F`/`--form`
* `-L`/`--location`
* `-u`/`--user`
* `-x`/`--proxy`
* `-U`/`--proxy-user`
* `--compressed`
The `curl` command prefix is optional
Expand Down
72 changes: 69 additions & 3 deletions lib/curl_req/macro.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ defmodule CurlReq.Macro do
form: :keep,
location: :boolean,
user: :string,
compressed: :boolean
compressed: :boolean,
proxy: :string,
proxy_user: :string
],
aliases: [
H: :header,
Expand All @@ -35,7 +37,9 @@ defmodule CurlReq.Macro do
I: :head,
F: :form,
L: :location,
u: :user
u: :user,
x: :proxy,
U: :proxy_user
]
)

Expand Down Expand Up @@ -69,7 +73,7 @@ defmodule CurlReq.Macro do

%Req.Request{}
# Req would accept an URI struct but here we use to_string/1 because Req uses URI.parse/1 which sets the deprecated `authority` field which upsets the test assertions.
# Can be removed the Req uses URI.new/1
# Can be removed when Req uses URI.new/1
|> Req.merge(url: URI.to_string(url))
|> add_header(options)
|> add_method(options)
Expand All @@ -78,6 +82,7 @@ defmodule CurlReq.Macro do
|> add_form(options)
|> add_auth(options)
|> add_compression(options)
|> add_proxy(options)
|> configure_redirects(options)
end

Expand Down Expand Up @@ -188,6 +193,67 @@ defmodule CurlReq.Macro do
end
end

defp add_proxy(req, options) do
with proxy when is_binary(proxy) <- Keyword.get(options, :proxy),
%URI{scheme: scheme, port: port, host: host, userinfo: userinfo}
when scheme in ["http", "https"] <-
validate_proxy_uri(proxy) do
connect_options =
[
proxy: {String.to_existing_atom(scheme), host, port, []}
]
|> maybe_add_proxy_auth(options, userinfo)

req
|> Req.Request.register_options([
:connect_options
])
|> Req.merge(connect_options: connect_options)
else
_ -> req
end
end

defp validate_proxy_uri("http://" <> _rest = uri), do: URI.parse(uri)
defp validate_proxy_uri("https://" <> _rest = uri), do: URI.parse(uri)

defp validate_proxy_uri(uri) do
case String.split(uri, "://") do
[scheme, _uri] ->
raise ArgumentError, "Unsupported scheme #{scheme} for proxy in #{uri}"

[uri] ->
URI.parse("http://" <> uri)
end
end

defp maybe_add_proxy_auth(connect_options, options, nil) do
proxy_headers =
case Keyword.get(options, :proxy_user) do
nil ->
[]

credentials ->
[
proxy_headers: [
{"proxy-authorization", "Basic " <> Base.encode64(credentials)}
]
]
end

Keyword.merge(connect_options, proxy_headers)
end

defp maybe_add_proxy_auth(connect_options, _options, userinfo) do
proxy_headers = [
proxy_headers: [
{"proxy-authorization", "Basic " <> Base.encode64(userinfo)}
]
]

Keyword.merge(connect_options, proxy_headers)
end

defp configure_redirects(req, options) do
if Keyword.get(options, :location, false) do
req
Expand Down
53 changes: 53 additions & 0 deletions test/curl_req/macro_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,59 @@ defmodule CurlReq.MacroTest do
response_steps: [redirect: &Req.Steps.redirect/1]
}
end

test "proxy" do
assert ~CURL(curl --proxy my.proxy.com:22225 http://example.com) ==
%Req.Request{
url: URI.parse("http://example.com"),
registered_options: MapSet.new([:connect_options]),
options: %{
connect_options: [proxy: {:http, "my.proxy.com", 22225, []}]
}
}
end

test "proxy with basic auth" do
assert ~CURL(curl --proxy https://my.proxy.com:22225 --proxy-user foo:bar http://example.com) ==
%Req.Request{
url: URI.parse("http://example.com"),
registered_options: MapSet.new([:connect_options]),
options: %{
connect_options: [
proxy: {:https, "my.proxy.com", 22225, []},
proxy_headers: [
{"proxy-authorization", "Basic " <> Base.encode64("foo:bar")}
]
]
}
}
end

test "proxy with inline basic auth" do
assert ~CURL(curl --proxy https://foo:bar@my.proxy.com:22225 http://example.com) ==
%Req.Request{
url: URI.parse("http://example.com"),
registered_options: MapSet.new([:connect_options]),
options: %{
connect_options: [
proxy: {:https, "my.proxy.com", 22225, []},
proxy_headers: [
{"proxy-authorization", "Basic " <> Base.encode64("foo:bar")}
]
]
}
}
end

test "proxy raises on non http scheme uri" do
assert_raise(
ArgumentError,
"Unsupported scheme ssh for proxy in ssh://my.proxy.com:22225",
fn ->
CurlReq.Macro.parse("curl --proxy ssh://my.proxy.com:22225 http://example.com")
end
)
end
end

describe "newlines" do
Expand Down
23 changes: 23 additions & 0 deletions test/curl_req_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,28 @@ defmodule CurlReqTest do
Req.new(url: "https://example.com", auth: {:basic, "user:pass"})
|> CurlReq.to_curl()
end

test "proxy" do
assert ~S(curl --compressed -x "http://my.proxy.com:80" -X GET https://example.com) ==
Req.new(
url: "https://example.com",
connect_options: [proxy: {:http, "my.proxy.com", 80, []}]
)
|> CurlReq.to_curl()
end

test "proxy user" do
assert ~S(curl --compressed -x "http://my.proxy.com:80" -U foo:bar -X GET https://example.com) ==
Req.new(
url: "https://example.com",
connect_options: [
proxy: {:http, "my.proxy.com", 80, []},
proxy_headers: [
{"proxy-authorization", "Basic " <> Base.encode64("foo:bar")}
]
]
)
|> CurlReq.to_curl()
end
end
end

0 comments on commit 68e4549

Please sign in to comment.