Comments (6)
@lau I agree, there is currently no immediate actionable steps possible.
Thanks for the clarification and discussion.
from tzdata.
Hi. I would like to get rid of the hackney dependency if there was an alternative that could do the same job.
Erlang has a built in HTTP client: HTTPC. The reason to use hackney is security. HTTPC does not validate HTTPS requests.
One alternative I have considered is to maintain a service that signs tzdata releases, include a key with the Elixir Tzdata package so that it is possible to download over HTTP (as opposed to HTTPS) and still being able to validate the data. Users of Elixir Tzdata would then have to trust that service instead of IANA.
from tzdata.
@lau OK, I understand. Thanks for quick response!
Maintaining your own service seems like a lot of hassle, though... It would be great to have other suggestions from the community, maybe there is an alternative that that does not involve many dependencies.
Please keep this issue open for some time, we should collect ideas here. I will spend some time brainstorming on this.
from tzdata.
I'm closing it for now, but feel free to comment here and it can be reopened. Or open a new issue.
from tzdata.
@lau I found an article that describes how to enable certificate checking with :httpc
. https://blog.voltone.net/post/7
iex(4)> cacertfile = :code.priv_dir(:http_clients) ++ '/cacert.pem'
...
iex(5)> :httpc.set_options(socket_opts: [verify: :verify_peer, cacertfile: cacertfile])
:ok
iex(6)> :httpc.request('https://blog.voltone.net')
{:ok, {{'HTTP/1.1', 200, 'OK'}, ...}}
iex(7)> :httpc.request('https://selfsigned.voltone.net')
{:error,
{:failed_connect,
[{:to_address, {'selfsigned.voltone.net', 443}},
{:inet,
[:inet, {:verify, :verify_peer},
{:cacertfile,
'[...]/http_clients/_build/dev/lib/http_clients/priv/cacert.pem'}],
{:tls_alert, 'bad certificate'}}]}}
the trust store can be taken from certifi: https://github.com/certifi/erlang-certifi/tree/master/priv , like :hackney
already does it.
Wdyt? Just found it and wanted to share.
from tzdata.
here is a working implementation that actually checks SSL certificate validity without :hackney
defmodule Tzdata.Http do
@type header :: {String.t(), String.t()}
@type httpresponse ::
{:ok, integer, String.t(), [header]} | {:error, String.t()} | {:error, term}
@callback get(String.t()) :: httpresponse
@callback head(String.t()) :: httpresponse
end
defmodule Tzdata.HTTPCAdapter do
@behaviour Tzdata.Http
@impl Tzdata.Http
def get(url) do
setup()
with {:ok, {{_, status_code, _}, headers_as_charlists, body_as_charlist}} <-
:httpc.request(url |> convert_to_list) do
{:ok, status_code, convert_body(body_as_charlist), convert_headers(headers_as_charlists)}
end
end
@impl Tzdata.Http
def head(url) do
setup()
with {:ok, {{_, status_code, _}, headers_as_charlists, body_as_charlist}} <-
:httpc.request(:head, {url |> convert_to_list, []}, [], []) do
{:ok, status_code, convert_body(body_as_charlist), convert_headers(headers_as_charlists)}
end
end
defp convert_headers(headers) when is_list(headers) do
headers
|> Enum.map(fn {key, value} ->
{
key |> convert_header_key(),
value |> convert_to_string()
}
end)
end
defp setup do
:httpc.set_options(socket_opts: [verify: :verify_peer, cacertfile: certfile()])
end
defp certfile do
:code.priv_dir(:certifi) ++ '/cacerts.pem'
end
defp convert_header_key(key) do
key
|> List.to_string()
|> String.split("-")
|> Enum.map(&Macro.camelize(&1))
|> Enum.join("-")
end
defp convert_body(body) do
body |> convert_to_string()
end
defp convert_to_string(str) when is_binary(str), do: str
defp convert_to_string(list) when is_list(list), do: list |> List.to_string()
defp convert_to_list(str) when is_binary(str), do: str |> String.to_charlist()
defp convert_to_list(list) when is_list(list), do: list
end
defmodule Tzdata.HackneyAdapter do
@behaviour Tzdata.Http
@impl Tzdata.Http
def get(url) do
with {:ok, 200, headers, client_ref} <- :hackney.get(url, [], "", follow_redirect: true),
{:ok, body} <- :hackney.body(client_ref) do
{:ok, 200, body, headers}
end
end
@impl Tzdata.Http
def head(url) do
:hackney.head(url, [], "", [])
end
end
defmodule Runner do
def run_hackney do
run(Tzdata.HackneyAdapter)
end
def run_httpc do
run(Tzdata.HTTPCAdapter)
end
def run(adapter_module) do
adapter_module.get("https://data.iana.org/time-zones/tzdata-latest.tar.gz") |> IO.inspect()
adapter_module.head("https://data.iana.org/time-zones/tzdata-latest.tar.gz") |> IO.inspect()
end
end
running it:
iex(1)> Tzdata.HackneyAdapter.head("https://data.iana.org/time-zones/tzdata-latest.tar.gz")
{:ok, 200,
[
{"Accept-Ranges", "bytes"},
{"Cache-Control", "max-age=86400"},
{"Content-Type", "application/x-gzip"},
{"Date", "Wed, 12 Dec 2018 10:45:23 GMT"},
{"Etag", "\"59748-57934b7bc096a\""},
{"Expires", "Thu, 13 Dec 2018 10:45:23 GMT"},
{"Last-Modified", "Sat, 27 Oct 2018 12:10:11 GMT"},
{"Referrer-Policy", "origin-when-cross-origin"},
{"Server", "ECAcc (mic/9B10)"},
{"Strict-Transport-Security", "max-age=48211200; preload"},
{"X-Cache", "HIT"},
{"X-Frame-Options", "SAMEORIGIN"},
{"Content-Length", "366408"}
]}
iex(2)> Tzdata.HTTPCAdapter.head("https://data.iana.org/time-zones/tzdata-latest.tar.gz")
{:ok, 200,
[
{"Cache-Control", "max-age=86400"},
{"Date", "Wed, 12 Dec 2018 10:45:27 GMT"},
{"Accept-Ranges", "bytes"},
{"Etag", "\"59748-57934b7bc096a\""},
{"Server", "ECAcc (mic/9B10)"},
{"Content-Length", "366408"},
{"Content-Type", "application/x-gzip"},
{"Expires", "Thu, 13 Dec 2018 10:45:27 GMT"},
{"Last-Modified", "Sat, 27 Oct 2018 12:10:11 GMT"},
{"Referrer-Policy", "origin-when-cross-origin"},
{"Strict-Transport-Security", "max-age=48211200; preload"},
{"X-Cache", "HIT"},
{"X-Frame-Options", "SAMEORIGIN"}
]}
iex(3)> Tzdata.HackneyAdapter.get("https://data.iana.org/time-zones/tzdata-latest.tar.gz")
{:ok, 200,
<<31, 139, 8, 0, 0, 0, 0, 0, 2, 3, 236, 91, 203, 114, 227, 70, 150, 173, 109,
225, 43, 114, 228, 137, 144, 228, 224, 3, 32, 41, 82, 162, 219, 238, 214,
187, 202, 86, 149, 20, 69, 201, 158, 174, 137, 137, 138, ...>>,
[
{"Accept-Ranges", "bytes"},
{"Cache-Control", "max-age=86400"},
{"Content-Type", "application/x-gzip"},
{"Date", "Wed, 12 Dec 2018 10:47:05 GMT"},
{"Etag", "\"59748-57934b7bc096a\""},
{"Expires", "Thu, 13 Dec 2018 10:47:05 GMT"},
{"Last-Modified", "Sat, 27 Oct 2018 12:10:11 GMT"},
{"Referrer-Policy", "origin-when-cross-origin"},
{"Server", "ECAcc (mic/9B10)"},
{"Strict-Transport-Security", "max-age=48211200; preload"},
{"X-Cache", "HIT"},
{"X-Frame-Options", "SAMEORIGIN"},
{"Content-Length", "366408"}
]}
iex(4)> Tzdata.HTTPCAdapter.get("https://data.iana.org/time-zones/tzdata-latest.tar.gz")
{:ok, 200,
<<31, 194, 139, 8, 0, 0, 0, 0, 0, 2, 3, 195, 172, 91, 195, 139, 114, 195, 163,
70, 194, 150, 194, 173, 109, 195, 161, 43, 114, 195, 164, 194, 137, 194, 144,
195, 164, 195, 160, 3, 32, 41, 82, 194, 162, 195, 155, ...>>,
[
{"Cache-Control", "max-age=86400"},
{"Date", "Wed, 12 Dec 2018 10:47:18 GMT"},
{"Accept-Ranges", "bytes"},
{"Etag", "\"59748-57934b7bc096a\""},
{"Server", "ECAcc (mic/9B10)"},
{"Content-Length", "366408"},
{"Content-Type", "application/x-gzip"},
{"Expires", "Thu, 13 Dec 2018 10:47:18 GMT"},
{"Last-Modified", "Sat, 27 Oct 2018 12:10:11 GMT"},
{"Referrer-Policy", "origin-when-cross-origin"},
{"Strict-Transport-Security", "max-age=48211200; preload"},
{"X-Cache", "HIT"},
{"X-Frame-Options", "SAMEORIGIN"}
]}
from tzdata.
Related Issues (20)
- Erlang 23 HTTPC support - update Hackney to ~> 1.16 HOT 2
- IANA data does not seem to be correctly parsed for America/Whitehorse (Canada/Yukon) and America/Dawson HOT 5
- Compiler warnings on Elixir 1.11.0
- Possible to force HTTP client Hackney
- Invalid gap for the first naive datetime in the gap range
- tzdata_current_release raise an argument error
- Method to convert an alias to a canonical zone? HOT 1
- Dropping hackney dependency HOT 10
- Tzdata 2.0 plans HOT 4
- GenServer Tzdata.EtsHolder terminating when update HOT 9
- GenServer :tzdata_release_updater terminating HOT 8
- (FunctionClauseError) no function clause matching in Keyword.delete_key/3 HOT 3
- Configurable download_url HOT 2
- Tzdata.ReleaseUpdater argument error HOT 3
- Argument Error on :ets.lookup in ReleaseReader
- UndefinedFunctionError or wrong gap when date-time hits exactly the start of a gap HOT 1
- Tzdata polling for update HOT 9
- Function to list possible offsets for a time zone HOT 1
- Question: why were the functions related to `zone1970.tab` data removed (module `TableData`)? HOT 4
- Ability to download fresh tz release during build HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tzdata.