update
This commit is contained in:
11
phoenix/deps/mint/.formatter.exs
Normal file
11
phoenix/deps/mint/.formatter.exs
Normal file
@@ -0,0 +1,11 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["mix.exs", "{lib,test}/**/*.{ex,exs}"],
|
||||
import_deps: [:stream_data],
|
||||
locals_without_parens: [
|
||||
assert_round_trip: 1,
|
||||
assert_recv_frames: 1,
|
||||
assert_http2_error: 2,
|
||||
assert_transport_error: 2
|
||||
]
|
||||
]
|
||||
BIN
phoenix/deps/mint/.hex
Normal file
BIN
phoenix/deps/mint/.hex
Normal file
Binary file not shown.
226
phoenix/deps/mint/CHANGELOG.md
Normal file
226
phoenix/deps/mint/CHANGELOG.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.1
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a bug introduced in the previous version that broke `Mint.HTTP.open?/1`.
|
||||
|
||||
## v1.7.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a bug with double-wrapping tunnel proxy errors. This only affected HTTP/1 connections using proxies when upgrade errors would happen—see [#438](https://github.com/elixir-mint/mint/issues/438).
|
||||
* Introduce `:skip_target_validation` option for HTTP/1.1 connections.
|
||||
* Add generic `:custom_error` to HTTP/2 frames. This can be returned by HTTP/2 servers in compliance with the HTTP/2 spec. Before, Mint would error out in such cases, while now it just returns the unaltered custom error code.
|
||||
* Fix compilation warning for the next Elixir release (1.19).
|
||||
|
||||
## v1.6.2
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Allow for version `~> 1.0` of the `hpax` dependency.
|
||||
|
||||
## v1.6.1
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Default to using Erlang certificate store (see [`public_key:cacerts_get/0`](https://www.erlang.org/doc/apps/public_key/public_key.html#cacerts_get-0) and friends) if available, instead of [CAStore](https://github.com/elixir-mint/castore).
|
||||
* Don't send `RST_STREAM` frames in HTTP/2 if they are not needed (this is a network optimization, not visible to users of Mint).
|
||||
|
||||
## v1.6.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add `:case_sensitive_headers` option to `Mint.HTTP1.connect/4`.
|
||||
* Add `:inet4` option to `Mint.HTTP.connect/4`.
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Require Elixir 1.11+.
|
||||
* Add `match_fun` clause to deal with IP addresses in TLS handshake.
|
||||
* Optimize creation of HTTP/2 requests.
|
||||
* Fix a compilation warning (unused `set_flag/2` function).
|
||||
* Improve performance of downcasing headers.
|
||||
* Deprecate `:read_write` option in `Mint.HTTP.open?/2`.
|
||||
* Improve performance of checking for the CAStore library.
|
||||
|
||||
## v1.5.2
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a memory leak with `Mint.HTTP1` connections which would stay open but
|
||||
report as closed on timeouts.
|
||||
|
||||
## v1.5.1
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a `FunctionClauseError` that would happen when calling
|
||||
`Mint.HTTP2.close/1` on an HTTP/2 connection that hadn't completed the
|
||||
handshake yet. This bug was introduced in v1.5.0. See [issue
|
||||
#392](https://github.com/elixir-mint/mint/issues/392).
|
||||
|
||||
## v1.5.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Properly close sockets on erroneous connections.
|
||||
* Fix `Mint.HTTP.is_connection_message/2` to support proxy connections.
|
||||
* Add support for CAStore v1.0.0+.
|
||||
* Support all HTTP/2 settings for clients as well (see
|
||||
`Mint.HTTP2.put_settings/2`).
|
||||
* Handle the first `SETTINGS` frame sent by the server *asynchronously* in
|
||||
HTTP/2. This means lower latency between connecting to a server and being
|
||||
able to send data to it.
|
||||
* Add more logging and make logging configurable through the `:log` option
|
||||
(see `Mint.HTTP.connect/4`, `Mint.HTTP1.connect/4`, `Mint.HTTP2.connect/4`).
|
||||
|
||||
## v1.4.2
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Properly handle interim responses (informational `1xx` status codes) in
|
||||
HTTP/2. Now you might get zero or more sequences of `:status` and `:headers`
|
||||
responses with status `1xx` before the *final response* (with status
|
||||
non-`1xx`).
|
||||
|
||||
## v1.4.1
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Emit the remaining buffer as a `:data` response when switching protocols
|
||||
from HTTP/1.
|
||||
* Respect closed-for-writing when streaming data frames in HTTP/2.
|
||||
* Fix handling of HTTP/2 frames of an unknown type.
|
||||
|
||||
## v1.4.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Add support for `SETTINGS_ENABLE_CONNECT_PROTOCOL` HTTP/2 server setting.
|
||||
* Omit the `:scheme` and `:path` pseudo headers for HTTP/2 CONNECT.
|
||||
* Fix invalid connection state when data can't be sent.
|
||||
* Skip expired certs in partial chain hook.
|
||||
* Add `Mint.HTTP.get_proxy_headers/1`.
|
||||
* Add `Mint.HTTP.module/1`.
|
||||
|
||||
## v1.3.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Improve compatibility with OTP 24.
|
||||
* Support HTTP/1 pipelining when streaming requests.
|
||||
* Add `Mint.HTTP.get_socket/1` for returning the connection socket.
|
||||
* Improve compatibility with TLS 1.3.
|
||||
|
||||
## v1.2.1
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a bug where we were not ignoring the return value of `:ssl.close/1` and `:gen_tcp.close/1`.
|
||||
* Fix a bug where we were not properly handling transport errors when doing ALPN protocol negotiation.
|
||||
* Fix a bug where we were not handling connection closed errors in a few places.
|
||||
|
||||
## v1.2.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a few bugs with passing the Mint connection around.
|
||||
* Add IPv6 support with `inet6: true` in the transport options.
|
||||
* Cache the `:cacertfile` option for faster certificate lookup and decoding.
|
||||
* Add TLS 1.3 to default versions.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Concatenate values in one `cookie` header if the `cookie` header is provided more than once in HTTP/2.
|
||||
* Fix headers merging in `Mint.UnsafeProxy`.
|
||||
* Remove some `Logger.debug/1` calls from the codebase.
|
||||
* Assume the HTTP/2 protocol on TCP connections if using `Mint.HTTP2`.
|
||||
* Fix a bug where we would send `WINDOW_UPDATE` frames with an increment of `0` in HTTP/2.
|
||||
* Make the empty body chunk a no-op for `Mint.HTTP.stream_request_body/3` (only for HTTP/1).
|
||||
* Add the `Mint.HTTP.is_connection_message/2` guard.
|
||||
* Fix wildcard certificate verification in OTP 23.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove the deprecated `Mint.HTTP.request/4`, `Mint.HTTP1.request/4`, and `Mint.HTTP2.request/4`.
|
||||
|
||||
## v0.5.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Deprecate `Mint.HTTP.request/4` in favor of explicitly passing the body every time in `Mint.HTTP.request/5`. Same for `Mint.HTTP1` and `Mint.HTTP2`.
|
||||
* Don't include port in the `authority` header if it's the default port for the used protocol.
|
||||
* Add a default `content-length` header in HTTP/2
|
||||
* Allow passing headers to proxies with the `:proxy_headers` option.
|
||||
* Fix a bug with HTTP/1 chunking.
|
||||
|
||||
## v0.4.0
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a small bug with double "wrapping" of some `Mint.TransportError`s.
|
||||
* Prevent unnecessary buffer allocations in the connections (less memory waste!).
|
||||
* Add support for chunked transfer-encoding in HTTP/1 requests when you don't use `content-encoding`/`transfer-encoding` yourself.
|
||||
* Add support for trailer headers in HTTP/* requests through `stream_request_body/3`.
|
||||
* Add a page about decompressing responses in the guides.
|
||||
|
||||
## v0.3.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove `Mint.HTTP1.get_socket/1`, `Mint.HTTP2.get_socket/1`, and `Mint.HTTP.get_socket/1`.
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Downcase all headers in HTTP/2 to mimic the behavior in HTTP/1.1.
|
||||
|
||||
* Add `Mint.HTTP.set_mode/2`, `Mint.HTTP1.set_mode/2`, and `Mint.HTTP2.set_mode/2` to change the mode of a socket between active and passive.
|
||||
|
||||
* Add a `:mode` option to the `connect/4` functions to start the socket in active or passive mode.
|
||||
|
||||
* Add `Mint.HTTP.recv/3`, `Mint.HTTP1.recv/3`, and `Mint.HTTP2.recv/3` to receive data from a passive socket in a blocking way.
|
||||
|
||||
* Add `Mint.HTTP.controlling_process/2`, `Mint.HTTP1.controlling_process/2`, and `Mint.HTTP2.controlling_process/2` to change the controlling process of a connection.
|
||||
|
||||
* Support trailer response headers in HTTP/2 connections.
|
||||
|
||||
## v0.2.1
|
||||
|
||||
### Bug Fixes and Improvements
|
||||
|
||||
* Fix a bug with requests exceeding the window size in HTTP/2. We were sending the headers of a request even if the body was larger than the window size. Now, if the body is larger than the window size, we error out right away.
|
||||
|
||||
* Fix a bug in the HTTP/2 handshake that would crash the connection in case the server sent unforeseen frames.
|
||||
|
||||
* Improve buffering of body chunks in HTTP/1.
|
||||
|
||||
## v0.2.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Add the `Mint.TransportError` and `Mint.HTTPError` exceptions. Change all the connection functions so that they return these error structs instead of generic terms.
|
||||
* Remove `Mint.HTTP2.get_setting/2` in favour of `Mint.HTTP2.get_server_setting/2` and `Mint.HTTP2.get_client_setting/2`.
|
||||
|
||||
### Bug fixes and enhancements
|
||||
|
||||
* Add support for HTTP/2 server push with the new `:push_promise` response.
|
||||
* Add `Mint.HTTP2.cancel_request/5`.
|
||||
* Add `Mint.HTTP2.get_window_size/2`.
|
||||
* Add `open_request_count/1` function to `Mint.HTTP`, and `Mint.HTTP1`, `Mint.HTTP2`.
|
||||
* Add `open?/2` function to `Mint.HTTP`, and `Mint.HTTP1`, `Mint.HTTP2`.
|
||||
* Make the `Mint.HTTP2.HPACK` module private.
|
||||
* Take into account the max header list size advertised by the server in HTTP/2 connections.
|
||||
* Improve error handling in a bunch of `Mint.HTTP2` functions.
|
||||
* Fix flow control on `WINDOW_UPDATE` frames at the connection level in `Mint.HTTP2`.
|
||||
* Correctly return timeout errors when connecting.
|
||||
* Treat HTTP/1 header keys as case-insensitive.
|
||||
* Prohibit users from streaming on unknown requests in HTTP/2.
|
||||
* Prohibit the server from violating the client's max concurrent streams setting in HTTP/2.
|
||||
* Strip whitespace when parsing the `content-length` header in HTTP/1.
|
||||
* Fix path validation when building HTTP/1 requests, fixes paths with `%NN` escapes.
|
||||
176
phoenix/deps/mint/LICENSE.txt
Normal file
176
phoenix/deps/mint/LICENSE.txt
Normal file
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
158
phoenix/deps/mint/README.md
Normal file
158
phoenix/deps/mint/README.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Mint 🌱
|
||||
|
||||
[](https://github.com/elixir-mint/mint/actions/workflows/main.yml)
|
||||
[][documentation]
|
||||
[](https://hex.pm/packages/mint)
|
||||
[](https://coveralls.io/github/elixir-mint/mint?branch=main)
|
||||
|
||||
> Functional, low-level HTTP client for Elixir with support for HTTP/1 and HTTP/2.
|
||||
|
||||
## Installation
|
||||
|
||||
To install Mint, add it to your `mix.exs` file:
|
||||
|
||||
```elixir
|
||||
defp deps do
|
||||
[
|
||||
{:mint, "~> 1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Then, run:
|
||||
|
||||
```sh
|
||||
mix deps.get
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Mint is different from most Erlang and Elixir HTTP clients because it provides a *process-less architecture*. Instead, Mint is based on a functional and immutable data structure that represents an HTTP connection. This data structure wraps a TCP or SSL socket. This allows for more fine-tailored architectures where the developer is responsible for wrapping the connection struct, such as having one process handle multiple connections or having different kinds of processes handle connections. You can think of Mint as [`:gen_tcp`](https://erlang.org/doc/man/gen_tcp.html) and [`:ssl`](https://www.erlang.org/doc/man/ssl.html), but with an understanding of the HTTP/1.1 and HTTP/2 protocols.
|
||||
|
||||
Let's see an example of a basic interaction with Mint. First, we start a connection through `Mint.HTTP.connect/3`:
|
||||
|
||||
```elixir
|
||||
iex> {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80)
|
||||
```
|
||||
|
||||
This transparently chooses between HTTP/1 and HTTP/2. Then, we can send requests with:
|
||||
|
||||
```elixir
|
||||
iex> {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", [], "")
|
||||
```
|
||||
|
||||
The connection socket runs in [*active mode*](http://erlang.org/doc/man/inet.html#setopts-2) (with `active: :once`), which means that the user of the library needs to handle [TCP messages](http://erlang.org/doc/man/gen_tcp.html#connect-4) and [SSL messages](http://erlang.org/doc/man/ssl.html#id66002):
|
||||
|
||||
```elixir
|
||||
iex> flush()
|
||||
{:tcp, #Port<0.8>,
|
||||
"HTTP/1.1 200 OK\r\n" <> _}
|
||||
```
|
||||
|
||||
Users are not supposed to examine these messages. Instead, Mint provides a `stream/2` function that turns messages into HTTP responses. Mint streams responses back to the user in parts through response parts such as `:status`, `:headers`, `:data`, and `:done`.
|
||||
|
||||
|
||||
```elixir
|
||||
iex> {:ok, conn} = Mint.HTTP.connect(:https, "httpbin.org", 443)
|
||||
iex> {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", [], "")
|
||||
iex> receive do
|
||||
...> message ->
|
||||
...> {:ok, conn, responses} = Mint.HTTP.stream(conn, message)
|
||||
...> IO.inspect(responses)
|
||||
...> end
|
||||
[
|
||||
{:status, #Reference<...>, 200},
|
||||
{:headers, #Reference<...>, [{"connection", "keep-alive"}, ...},
|
||||
{:data, #Reference<...>, "<!DOCTYPE html>..."},
|
||||
{:done, #Reference<...>}
|
||||
]
|
||||
```
|
||||
|
||||
In the example above, we get all the responses as a single SSL message, but that might not always be the case. This means that `Mint.HTTP.stream/2` might not always return responses.
|
||||
|
||||
The connection API is *stateless*, which means that you need to make sure to always save the connection that functions return:
|
||||
|
||||
```elixir
|
||||
# Wrong ❌
|
||||
{:ok, _conn, ref} = Mint.HTTP.request(conn, "GET", "/foo", [], "")
|
||||
{:ok, conn, ref} = Mint.HTTP.request(conn, "GET", "/bar", [], "")
|
||||
|
||||
# Correct ✅
|
||||
{:ok, conn, ref} = Mint.HTTP.request(conn, "GET", "/foo", [], "")
|
||||
{:ok, conn, ref} = Mint.HTTP.request(conn, "GET", "/bar", [], "")
|
||||
```
|
||||
|
||||
For more information, see [the documentation][documentation].
|
||||
|
||||
### SSL certificates
|
||||
|
||||
When using SSL, you can pass in your own CA certificate store. If one is not provided, Mint will use the one in your system, as long as you are using Erlang/OTP 25+. If none of these conditions are true, just add `:castore` to your dependencies.
|
||||
|
||||
```elixir
|
||||
defp deps do
|
||||
[
|
||||
# ...,
|
||||
{:castore, "~> 1.0.0"},
|
||||
{:mint, "~> 0.4.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
### WebSocket Support
|
||||
|
||||
Mint itself does not support the WebSocket protocol, but it can be used as the foundation to build a WebSocket client on top of. If you need WebSocket support, you can use [mint_web_socket].
|
||||
|
||||
### Connection Management and Pooling
|
||||
|
||||
Mint is a low-level client. If you need higher-level features such as connection management, pooling, metrics, and more, check out [Finch], a project built on top of Mint that provides those things.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you wish to contribute, check out the [issue list][issues] and let us know what you want to work on, so that we can discuss it and reduce duplicate work.
|
||||
|
||||
Tests are organized with tags. Integration tests that hit real websites over the internet are tagged with `:requires_internet_connection`. Proxy tests are tagged with `:proxy` (excluded by default) and require that you start local proxy instance through Docker Compose (or Podman Compose) from the Mint root directory in order to run.
|
||||
|
||||
If you encounter `{:error, %Mint.TransportError{reason: :nxdomain}}` error during the integration test, this suggests your DNS-based adblocker (self-hosted or VPN) might be blocking social media sites used in the integration test cases.
|
||||
|
||||
Here are a few examples of running tests:
|
||||
|
||||
Run all default tests which including `:requires_internet_connection` except `:proxy`:
|
||||
|
||||
```sh
|
||||
mix test
|
||||
```
|
||||
|
||||
Local tests, if you don't have an Internet connection available:
|
||||
|
||||
```sh
|
||||
mix test --exclude requires_internet_connection
|
||||
```
|
||||
|
||||
Run all tests:
|
||||
|
||||
```sh
|
||||
DOCKER_USER="$UID:$GID" docker compose up --detach # or podman-compose up --detach
|
||||
mix test --include proxy
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2018 Eric Meadows-Jönsson and Andrea Leopardi
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
[castore]: https://github.com/elixir-mint/castore
|
||||
[documentation]: https://hexdocs.pm/mint
|
||||
[issues]: https://github.com/elixir-mint/mint/issues
|
||||
[mint_web_socket]: https://github.com/elixir-mint/mint_web_socket
|
||||
[Finch]: https://github.com/sneako/finch
|
||||
35
phoenix/deps/mint/hex_metadata.config
Normal file
35
phoenix/deps/mint/hex_metadata.config
Normal file
@@ -0,0 +1,35 @@
|
||||
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-mint/mint">>}]}.
|
||||
{<<"name">>,<<"mint">>}.
|
||||
{<<"version">>,<<"1.7.1">>}.
|
||||
{<<"description">>,<<"Small and composable HTTP client.">>}.
|
||||
{<<"elixir">>,<<"~> 1.12">>}.
|
||||
{<<"app">>,<<"mint">>}.
|
||||
{<<"licenses">>,[<<"Apache-2.0">>]}.
|
||||
{<<"requirements">>,
|
||||
[[{<<"name">>,<<"castore">>},
|
||||
{<<"app">>,<<"castore">>},
|
||||
{<<"optional">>,true},
|
||||
{<<"requirement">>,<<"~> 0.1.0 or ~> 1.0">>},
|
||||
{<<"repository">>,<<"hexpm">>}],
|
||||
[{<<"name">>,<<"hpax">>},
|
||||
{<<"app">>,<<"hpax">>},
|
||||
{<<"optional">>,false},
|
||||
{<<"requirement">>,<<"~> 0.1.1 or ~> 0.2.0 or ~> 1.0">>},
|
||||
{<<"repository">>,<<"hexpm">>}]]}.
|
||||
{<<"files">>,
|
||||
[<<"lib">>,<<"lib/mint">>,<<"lib/mint/http1">>,
|
||||
<<"lib/mint/http1/response.ex">>,<<"lib/mint/http1/request.ex">>,
|
||||
<<"lib/mint/http1/parse.ex">>,<<"lib/mint/http2.ex">>,
|
||||
<<"lib/mint/tunnel_proxy.ex">>,<<"lib/mint/core">>,
|
||||
<<"lib/mint/core/util.ex">>,<<"lib/mint/core/transport">>,
|
||||
<<"lib/mint/core/transport/ssl.ex">>,<<"lib/mint/core/transport/tcp.ex">>,
|
||||
<<"lib/mint/core/transport.ex">>,<<"lib/mint/core/headers.ex">>,
|
||||
<<"lib/mint/core/conn.ex">>,<<"lib/mint/types.ex">>,
|
||||
<<"lib/mint/http_error.ex">>,<<"lib/mint/http2">>,
|
||||
<<"lib/mint/http2/frame.ex">>,<<"lib/mint/negotiate.ex">>,
|
||||
<<"lib/mint/http1.ex">>,<<"lib/mint/application.ex">>,
|
||||
<<"lib/mint/http.ex">>,<<"lib/mint/unsafe_proxy.ex">>,
|
||||
<<"lib/mint/transport_error.ex">>,<<".formatter.exs">>,<<"mix.exs">>,
|
||||
<<"README.md">>,<<"LICENSE.txt">>,<<"CHANGELOG.md">>,<<"src">>,
|
||||
<<"src/mint_shims.erl">>]}.
|
||||
{<<"build_tools">>,[<<"mix">>]}.
|
||||
14
phoenix/deps/mint/lib/mint/application.ex
Normal file
14
phoenix/deps/mint/lib/mint/application.ex
Normal file
@@ -0,0 +1,14 @@
|
||||
defmodule Mint.Application do
|
||||
@moduledoc false
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
persistent_term =
|
||||
Code.ensure_loaded?(:persistent_term) and function_exported?(:persistent_term, :get, 2)
|
||||
|
||||
Application.put_env(:mint, :persistent_term, persistent_term)
|
||||
|
||||
opts = [strategy: :one_for_one, name: Mint.Supervisor]
|
||||
Supervisor.start_link([], opts)
|
||||
end
|
||||
end
|
||||
65
phoenix/deps/mint/lib/mint/core/conn.ex
Normal file
65
phoenix/deps/mint/lib/mint/core/conn.ex
Normal file
@@ -0,0 +1,65 @@
|
||||
defmodule Mint.Core.Conn do
|
||||
@moduledoc false
|
||||
|
||||
alias Mint.Types
|
||||
|
||||
@type conn() :: term()
|
||||
|
||||
@callback initiate(
|
||||
module(),
|
||||
Mint.Types.socket(),
|
||||
String.t(),
|
||||
:inet.port_number(),
|
||||
keyword()
|
||||
) :: {:ok, conn()} | {:error, Types.error()}
|
||||
|
||||
@callback open?(conn(), :read | :write) :: boolean()
|
||||
|
||||
@callback close(conn()) :: {:ok, conn()}
|
||||
|
||||
@callback request(
|
||||
conn(),
|
||||
method :: String.t(),
|
||||
path :: String.t(),
|
||||
Types.headers(),
|
||||
body :: iodata() | nil | :stream
|
||||
) ::
|
||||
{:ok, conn(), Types.request_ref()}
|
||||
| {:error, conn(), Types.error()}
|
||||
|
||||
@callback stream_request_body(
|
||||
conn(),
|
||||
Types.request_ref(),
|
||||
body_chunk :: iodata() | :eof | {:eof, trailer_headers :: Types.headers()}
|
||||
) ::
|
||||
{:ok, conn()} | {:error, conn(), Types.error()}
|
||||
|
||||
@callback stream(conn(), term()) ::
|
||||
{:ok, conn(), [Types.response()]}
|
||||
| {:error, conn(), Types.error(), [Types.response()]}
|
||||
| :unknown
|
||||
|
||||
@callback open_request_count(conn()) :: non_neg_integer()
|
||||
|
||||
@callback recv(conn(), byte_count :: non_neg_integer(), timeout()) ::
|
||||
{:ok, conn(), [Types.response()]}
|
||||
| {:error, conn(), Types.error(), [Types.response()]}
|
||||
|
||||
@callback set_mode(conn(), :active | :passive) :: {:ok, conn()} | {:error, Types.error()}
|
||||
|
||||
@callback controlling_process(conn(), pid()) :: {:ok, conn()} | {:error, Types.error()}
|
||||
|
||||
@callback put_private(conn(), key :: atom(), value :: term()) :: conn()
|
||||
|
||||
@callback get_private(conn(), key :: atom(), default_value :: term()) :: term()
|
||||
|
||||
@callback delete_private(conn(), key :: atom()) :: conn()
|
||||
|
||||
@callback get_socket(conn()) :: Mint.Types.socket()
|
||||
|
||||
@callback get_proxy_headers(conn()) :: Mint.Types.headers()
|
||||
|
||||
@callback put_proxy_headers(conn(), Mint.Types.headers()) :: conn()
|
||||
|
||||
@callback put_log(conn(), boolean()) :: conn()
|
||||
end
|
||||
136
phoenix/deps/mint/lib/mint/core/headers.ex
Normal file
136
phoenix/deps/mint/lib/mint/core/headers.ex
Normal file
@@ -0,0 +1,136 @@
|
||||
defmodule Mint.Core.Headers do
|
||||
@moduledoc false
|
||||
|
||||
@type canonical() ::
|
||||
{original_name :: String.t(), canonical_name :: String.t(), value :: String.t()}
|
||||
@type raw() :: {original_name :: String.t(), value :: String.t()}
|
||||
|
||||
@unallowed_trailers MapSet.new([
|
||||
"content-encoding",
|
||||
"content-length",
|
||||
"content-range",
|
||||
"content-type",
|
||||
"trailer",
|
||||
"transfer-encoding",
|
||||
|
||||
# Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1)
|
||||
"cache-control",
|
||||
"expect",
|
||||
"host",
|
||||
"max-forwards",
|
||||
"pragma",
|
||||
"range",
|
||||
"te",
|
||||
|
||||
# Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2)
|
||||
"if-match",
|
||||
"if-none-match",
|
||||
"if-modified-since",
|
||||
"if-unmodified-since",
|
||||
"if-range",
|
||||
|
||||
# Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3)
|
||||
"authorization",
|
||||
"proxy-authenticate",
|
||||
"proxy-authorization",
|
||||
"www-authenticate",
|
||||
|
||||
# Cookie management (https://tools.ietf.org/html/rfc6265)
|
||||
"cookie",
|
||||
"set-cookie",
|
||||
|
||||
# Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1)
|
||||
"age",
|
||||
"cache-control",
|
||||
"expires",
|
||||
"date",
|
||||
"location",
|
||||
"retry-after",
|
||||
"vary",
|
||||
"warning"
|
||||
])
|
||||
|
||||
@spec from_raw([raw()]) :: [canonical()]
|
||||
def from_raw(headers) do
|
||||
Enum.map(headers, fn {name, value} -> {name, lower_raw(name), value} end)
|
||||
end
|
||||
|
||||
@spec to_raw([canonical()], boolean()) :: [raw()]
|
||||
def to_raw(headers, _case_sensitive = true) do
|
||||
Enum.map(headers, fn {name, _canonical_name, value} -> {name, value} end)
|
||||
end
|
||||
|
||||
def to_raw(headers, _case_sensitive = false) do
|
||||
Enum.map(headers, fn {_name, canonical_name, value} ->
|
||||
{canonical_name, value}
|
||||
end)
|
||||
end
|
||||
|
||||
@spec find([canonical()], String.t()) :: {String.t(), String.t()} | nil
|
||||
def find(headers, name) do
|
||||
case List.keyfind(headers, name, 1) do
|
||||
nil -> nil
|
||||
{name, _canonical_name, value} -> {name, value}
|
||||
end
|
||||
end
|
||||
|
||||
@spec replace([canonical()], String.t(), String.t(), String.t()) ::
|
||||
[canonical()]
|
||||
def replace(headers, new_name, canonical_name, value) do
|
||||
List.keyreplace(headers, canonical_name, 1, {new_name, canonical_name, value})
|
||||
end
|
||||
|
||||
@spec has?([canonical()], String.t()) :: boolean()
|
||||
def has?(headers, name) do
|
||||
List.keymember?(headers, name, 1)
|
||||
end
|
||||
|
||||
@spec put_new([canonical()], String.t(), String.t(), String.t() | nil) ::
|
||||
[canonical()]
|
||||
def put_new(headers, _name, _canonical_name, nil) do
|
||||
headers
|
||||
end
|
||||
|
||||
def put_new(headers, name, canonical_name, value) do
|
||||
if List.keymember?(headers, canonical_name, 1) do
|
||||
headers
|
||||
else
|
||||
[{name, canonical_name, value} | headers]
|
||||
end
|
||||
end
|
||||
|
||||
@spec put_new([canonical()], String.t(), String.t(), (-> String.t())) ::
|
||||
[canonical()]
|
||||
def put_new_lazy(headers, name, canonical_name, fun) do
|
||||
if List.keymember?(headers, canonical_name, 1) do
|
||||
headers
|
||||
else
|
||||
[{name, canonical_name, fun.()} | headers]
|
||||
end
|
||||
end
|
||||
|
||||
@spec find_unallowed_trailer([canonical()]) :: String.t() | nil
|
||||
def find_unallowed_trailer(headers) do
|
||||
Enum.find_value(headers, fn
|
||||
{raw_name, canonical_name, _value} ->
|
||||
if canonical_name in @unallowed_trailers do
|
||||
raw_name
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@spec remove_unallowed_trailer([raw()]) :: [raw()]
|
||||
def remove_unallowed_trailer(headers) do
|
||||
Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailers end)
|
||||
end
|
||||
|
||||
@spec lower_raw(String.t()) :: String.t()
|
||||
def lower_raw(name) do
|
||||
String.downcase(name, :ascii)
|
||||
end
|
||||
|
||||
@spec lower_raws([raw()]) :: [raw()]
|
||||
def lower_raws(headers) do
|
||||
Enum.map(headers, fn {name, value} -> {lower_raw(name), value} end)
|
||||
end
|
||||
end
|
||||
36
phoenix/deps/mint/lib/mint/core/transport.ex
Normal file
36
phoenix/deps/mint/lib/mint/core/transport.ex
Normal file
@@ -0,0 +1,36 @@
|
||||
defmodule Mint.Core.Transport do
|
||||
@moduledoc false
|
||||
|
||||
@type error() :: {:error, %Mint.TransportError{}}
|
||||
|
||||
alias Mint.Types
|
||||
|
||||
@callback connect(address :: Types.address(), port :: :inet.port_number(), opts :: keyword()) ::
|
||||
{:ok, Types.socket()} | error()
|
||||
|
||||
@callback upgrade(
|
||||
Types.socket(),
|
||||
original_scheme :: Types.scheme(),
|
||||
hostname :: String.t(),
|
||||
:inet.port_number(),
|
||||
opts :: keyword()
|
||||
) :: {:ok, Types.socket()} | error()
|
||||
|
||||
@callback negotiated_protocol(Types.socket()) ::
|
||||
{:ok, protocol :: binary()} | {:error, :protocol_not_negotiated}
|
||||
|
||||
@callback send(Types.socket(), payload :: iodata()) :: :ok | error()
|
||||
|
||||
@callback close(Types.socket()) :: :ok | error()
|
||||
|
||||
@callback recv(Types.socket(), bytes :: non_neg_integer(), timeout()) ::
|
||||
{:ok, binary()} | error()
|
||||
|
||||
@callback controlling_process(Types.socket(), pid()) :: :ok | error()
|
||||
|
||||
@callback setopts(Types.socket(), opts :: keyword()) :: :ok | error()
|
||||
|
||||
@callback getopts(Types.socket(), opts :: keyword()) :: {:ok, opts :: keyword()} | error()
|
||||
|
||||
@callback wrap_error(reason :: term()) :: %Mint.TransportError{}
|
||||
end
|
||||
755
phoenix/deps/mint/lib/mint/core/transport/ssl.ex
Normal file
755
phoenix/deps/mint/lib/mint/core/transport/ssl.ex
Normal file
@@ -0,0 +1,755 @@
|
||||
defmodule Mint.Core.Transport.SSL do
|
||||
@moduledoc false
|
||||
|
||||
require Logger
|
||||
require Record
|
||||
|
||||
@behaviour Mint.Core.Transport
|
||||
|
||||
# From RFC7540 appendix A
|
||||
@blocked_ciphers MapSet.new([
|
||||
{:null, :null, :null},
|
||||
{:rsa, :null, :md5},
|
||||
{:rsa, :null, :sha},
|
||||
{:rsa_export, :rc4_40, :md5},
|
||||
{:rsa, :rc4_128, :md5},
|
||||
{:rsa, :rc4_128, :sha},
|
||||
{:rsa_export, :rc2_cbc_40, :md5},
|
||||
{:rsa, :idea_cbc, :sha},
|
||||
{:rsa_export, :des40_cbc, :sha},
|
||||
{:rsa, :des_cbc, :sha},
|
||||
{:rsa, :"3des_ede_cbc", :sha},
|
||||
{:dh_dss_export, :des40_cbc, :sha},
|
||||
{:dh_dss, :des_cbc, :sha},
|
||||
{:dh_dss, :"3des_ede_cbc", :sha},
|
||||
{:dh_rsa_export, :des40_cbc, :sha},
|
||||
{:dh_rsa, :des_cbc, :sha},
|
||||
{:dh_rsa, :"3des_ede_cbc", :sha},
|
||||
{:dhe_dss_export, :des40_cbc, :sha},
|
||||
{:dhe_dss, :des_cbc, :sha},
|
||||
{:dhe_dss, :"3des_ede_cbc", :sha},
|
||||
{:dhe_rsa_export, :des40_cbc, :sha},
|
||||
{:dhe_rsa, :des_cbc, :sha},
|
||||
{:dhe_rsa, :"3des_ede_cbc", :sha},
|
||||
{:dh_anon_export, :rc4_40, :md5},
|
||||
{:dh_anon, :rc4_128, :md5},
|
||||
{:dh_anon_export, :des40_cbc, :sha},
|
||||
{:dh_anon, :des_cbc, :sha},
|
||||
{:dh_anon, :"3des_ede_cbc", :sha},
|
||||
{:krb5, :des_cbc, :sha},
|
||||
{:krb5, :"3des_ede_cbc", :sha},
|
||||
{:krb5, :rc4_128, :sha},
|
||||
{:krb5, :idea_cbc, :sha},
|
||||
{:krb5, :des_cbc, :md5},
|
||||
{:krb5, :"3des_ede_cbc", :md5},
|
||||
{:krb5, :rc4_128, :md5},
|
||||
{:krb5, :idea_cbc, :md5},
|
||||
{:krb5_export, :des_cbc_40, :sha},
|
||||
{:krb5_export, :rc2_cbc_40, :sha},
|
||||
{:krb5_export, :rc4_40, :sha},
|
||||
{:krb5_export, :des_cbc_40, :md5},
|
||||
{:krb5_export, :rc2_cbc_40, :md5},
|
||||
{:krb5_export, :rc4_40, :md5},
|
||||
{:psk, :null, :sha},
|
||||
{:dhe_psk, :null, :sha},
|
||||
{:rsa_psk, :null, :sha},
|
||||
{:rsa, :aes_128_cbc, :sha},
|
||||
{:dh_dss, :aes_128_cbc, :sha},
|
||||
{:dh_rsa, :aes_128_cbc, :sha},
|
||||
{:dhe_dss, :aes_128_cbc, :sha},
|
||||
{:dhe_rsa, :aes_128_cbc, :sha},
|
||||
{:dh_anon, :aes_128_cbc, :sha},
|
||||
{:rsa, :aes_256_cbc, :sha},
|
||||
{:dh_dss, :aes_256_cbc, :sha},
|
||||
{:dh_rsa, :aes_256_cbc, :sha},
|
||||
{:dhe_dss, :aes_256_cbc, :sha},
|
||||
{:dhe_rsa, :aes_256_cbc, :sha},
|
||||
{:dh_anon, :aes_256_cbc, :sha},
|
||||
{:rsa, :null, :sha256},
|
||||
{:rsa, :aes_128_cbc, :sha256},
|
||||
{:rsa, :aes_256_cbc, :sha256},
|
||||
{:dh_dss, :aes_128_cbc, :sha256},
|
||||
{:dh_rsa, :aes_128_cbc, :sha256},
|
||||
{:dhe_dss, :aes_128_cbc, :sha256},
|
||||
{:rsa, :camellia_128_cbc, :sha},
|
||||
{:dh_dss, :camellia_128_cbc, :sha},
|
||||
{:dh_rsa, :camellia_128_cbc, :sha},
|
||||
{:dhe_dss, :camellia_128_cbc, :sha},
|
||||
{:dhe_rsa, :camellia_128_cbc, :sha},
|
||||
{:dh_anon, :camellia_128_cbc, :sha},
|
||||
{:dhe_rsa, :aes_128_cbc, :sha256},
|
||||
{:dh_dss, :aes_256_cbc, :sha256},
|
||||
{:dh_rsa, :aes_256_cbc, :sha256},
|
||||
{:dhe_dss, :aes_256_cbc, :sha256},
|
||||
{:dhe_rsa, :aes_256_cbc, :sha256},
|
||||
{:dh_anon, :aes_128_cbc, :sha256},
|
||||
{:dh_anon, :aes_256_cbc, :sha256},
|
||||
{:rsa, :camellia_256_cbc, :sha},
|
||||
{:dh_dss, :camellia_256_cbc, :sha},
|
||||
{:dh_rsa, :camellia_256_cbc, :sha},
|
||||
{:dhe_dss, :camellia_256_cbc, :sha},
|
||||
{:dhe_rsa, :camellia_256_cbc, :sha},
|
||||
{:dh_anon, :camellia_256_cbc, :sha},
|
||||
{:psk, :rc4_128, :sha},
|
||||
{:psk, :"3des_ede_cbc", :sha},
|
||||
{:psk, :aes_128_cbc, :sha},
|
||||
{:psk, :aes_256_cbc, :sha},
|
||||
{:dhe_psk, :rc4_128, :sha},
|
||||
{:dhe_psk, :"3des_ede_cbc", :sha},
|
||||
{:dhe_psk, :aes_128_cbc, :sha},
|
||||
{:dhe_psk, :aes_256_cbc, :sha},
|
||||
{:rsa_psk, :rc4_128, :sha},
|
||||
{:rsa_psk, :"3des_ede_cbc", :sha},
|
||||
{:rsa_psk, :aes_128_cbc, :sha},
|
||||
{:rsa_psk, :aes_256_cbc, :sha},
|
||||
{:rsa, :seed_cbc, :sha},
|
||||
{:dh_dss, :seed_cbc, :sha},
|
||||
{:dh_rsa, :seed_cbc, :sha},
|
||||
{:dhe_dss, :seed_cbc, :sha},
|
||||
{:dhe_rsa, :seed_cbc, :sha},
|
||||
{:dh_anon, :seed_cbc, :sha},
|
||||
{:rsa, :aes_128_gcm, :sha256},
|
||||
{:rsa, :aes_256_gcm, :sha384},
|
||||
{:dh_rsa, :aes_128_gcm, :sha256},
|
||||
{:dh_rsa, :aes_256_gcm, :sha384},
|
||||
{:dh_dss, :aes_128_gcm, :sha256},
|
||||
{:dh_dss, :aes_256_gcm, :sha384},
|
||||
{:dh_anon, :aes_128_gcm, :sha256},
|
||||
{:dh_anon, :aes_256_gcm, :sha384},
|
||||
{:psk, :aes_128_gcm, :sha256},
|
||||
{:psk, :aes_256_gcm, :sha384},
|
||||
{:rsa_psk, :aes_128_gcm, :sha256},
|
||||
{:rsa_psk, :aes_256_gcm, :sha384},
|
||||
{:psk, :aes_128_cbc, :sha256},
|
||||
{:psk, :aes_256_cbc, :sha384},
|
||||
{:psk, :null, :sha256},
|
||||
{:psk, :null, :sha384},
|
||||
{:dhe_psk, :aes_128_cbc, :sha256},
|
||||
{:dhe_psk, :aes_256_cbc, :sha384},
|
||||
{:dhe_psk, :null, :sha256},
|
||||
{:dhe_psk, :null, :sha384},
|
||||
{:rsa_psk, :aes_128_cbc, :sha256},
|
||||
{:rsa_psk, :aes_256_cbc, :sha384},
|
||||
{:rsa_psk, :null, :sha256},
|
||||
{:rsa_psk, :null, :sha384},
|
||||
{:rsa, :camellia_128_cbc, :sha256},
|
||||
{:dh_dss, :camellia_128_cbc, :sha256},
|
||||
{:dh_rsa, :camellia_128_cbc, :sha256},
|
||||
{:dhe_dss, :camellia_128_cbc, :sha256},
|
||||
{:dhe_rsa, :camellia_128_cbc, :sha256},
|
||||
{:dh_anon, :camellia_128_cbc, :sha256},
|
||||
{:rsa, :camellia_256_cbc, :sha256},
|
||||
{:dh_dss, :camellia_256_cbc, :sha256},
|
||||
{:dh_rsa, :camellia_256_cbc, :sha256},
|
||||
{:dhe_dss, :camellia_256_cbc, :sha256},
|
||||
{:dhe_rsa, :camellia_256_cbc, :sha256},
|
||||
{:dh_anon, :camellia_256_cbc, :sha256},
|
||||
{:ecdh_ecdsa, :null, :sha},
|
||||
{:ecdh_ecdsa, :rc4_128, :sha},
|
||||
{:ecdh_ecdsa, :"3des_ede_cbc", :sha},
|
||||
{:ecdh_ecdsa, :aes_128_cbc, :sha},
|
||||
{:ecdh_ecdsa, :aes_256_cbc, :sha},
|
||||
{:ecdhe_ecdsa, :null, :sha},
|
||||
{:ecdhe_ecdsa, :rc4_128, :sha},
|
||||
{:ecdhe_ecdsa, :"3des_ede_cbc", :sha},
|
||||
{:ecdhe_ecdsa, :aes_128_cbc, :sha},
|
||||
{:ecdhe_ecdsa, :aes_256_cbc, :sha},
|
||||
{:ecdh_rsa, :null, :sha},
|
||||
{:ecdh_rsa, :rc4_128, :sha},
|
||||
{:ecdh_rsa, :"3des_ede_cbc", :sha},
|
||||
{:ecdh_rsa, :aes_128_cbc, :sha},
|
||||
{:ecdh_rsa, :aes_256_cbc, :sha},
|
||||
{:ecdhe_rsa, :null, :sha},
|
||||
{:ecdhe_rsa, :rc4_128, :sha},
|
||||
{:ecdhe_rsa, :"3des_ede_cbc", :sha},
|
||||
{:ecdhe_rsa, :aes_128_cbc, :sha},
|
||||
{:ecdhe_rsa, :aes_256_cbc, :sha},
|
||||
{:ecdh_anon, :null, :sha},
|
||||
{:ecdh_anon, :rc4_128, :sha},
|
||||
{:ecdh_anon, :"3des_ede_cbc", :sha},
|
||||
{:ecdh_anon, :aes_128_cbc, :sha},
|
||||
{:ecdh_anon, :aes_256_cbc, :sha},
|
||||
{:srp_sha, :"3des_ede_cbc", :sha},
|
||||
{:srp_sha_rsa, :"3des_ede_cbc", :sha},
|
||||
{:srp_sha_dss, :"3des_ede_cbc", :sha},
|
||||
{:srp_sha, :aes_128_cbc, :sha},
|
||||
{:srp_sha_rsa, :aes_128_cbc, :sha},
|
||||
{:srp_sha_dss, :aes_128_cbc, :sha},
|
||||
{:srp_sha, :aes_256_cbc, :sha},
|
||||
{:srp_sha_rsa, :aes_256_cbc, :sha},
|
||||
{:srp_sha_dss, :aes_256_cbc, :sha},
|
||||
{:ecdhe_ecdsa, :aes_128_cbc, :sha256},
|
||||
{:ecdhe_ecdsa, :aes_256_cbc, :sha384},
|
||||
{:ecdh_ecdsa, :aes_128_cbc, :sha256},
|
||||
{:ecdh_ecdsa, :aes_256_cbc, :sha384},
|
||||
{:ecdhe_rsa, :aes_128_cbc, :sha256},
|
||||
{:ecdhe_rsa, :aes_256_cbc, :sha384},
|
||||
{:ecdh_rsa, :aes_128_cbc, :sha256},
|
||||
{:ecdh_rsa, :aes_256_cbc, :sha384},
|
||||
{:ecdh_ecdsa, :aes_128_gcm, :sha256},
|
||||
{:ecdh_ecdsa, :aes_256_gcm, :sha384},
|
||||
{:ecdh_rsa, :aes_128_gcm, :sha256},
|
||||
{:ecdh_rsa, :aes_256_gcm, :sha384},
|
||||
{:ecdhe_psk, :rc4_128, :sha},
|
||||
{:ecdhe_psk, :"3des_ede_cbc", :sha},
|
||||
{:ecdhe_psk, :aes_128_cbc, :sha},
|
||||
{:ecdhe_psk, :aes_256_cbc, :sha},
|
||||
{:ecdhe_psk, :aes_128_cbc, :sha256},
|
||||
{:ecdhe_psk, :aes_256_cbc, :sha384},
|
||||
{:ecdhe_psk, :null, :sha},
|
||||
{:ecdhe_psk, :null, :sha256},
|
||||
{:ecdhe_psk, :null, :sha384},
|
||||
{:rsa, :aria_128_cbc, :sha256},
|
||||
{:rsa, :aria_256_cbc, :sha384},
|
||||
{:dh_dss, :aria_128_cbc, :sha256},
|
||||
{:dh_dss, :aria_256_cbc, :sha384},
|
||||
{:dh_rsa, :aria_128_cbc, :sha256},
|
||||
{:dh_rsa, :aria_256_cbc, :sha384},
|
||||
{:dhe_dss, :aria_128_cbc, :sha256},
|
||||
{:dhe_dss, :aria_256_cbc, :sha384},
|
||||
{:dhe_rsa, :aria_128_cbc, :sha256},
|
||||
{:dhe_rsa, :aria_256_cbc, :sha384},
|
||||
{:dh_anon, :aria_128_cbc, :sha256},
|
||||
{:dh_anon, :aria_256_cbc, :sha384},
|
||||
{:ecdhe_ecdsa, :aria_128_cbc, :sha256},
|
||||
{:ecdhe_ecdsa, :aria_256_cbc, :sha384},
|
||||
{:ecdh_ecdsa, :aria_128_cbc, :sha256},
|
||||
{:ecdh_ecdsa, :aria_256_cbc, :sha384},
|
||||
{:ecdhe_rsa, :aria_128_cbc, :sha256},
|
||||
{:ecdhe_rsa, :aria_256_cbc, :sha384},
|
||||
{:ecdh_rsa, :aria_128_cbc, :sha256},
|
||||
{:ecdh_rsa, :aria_256_cbc, :sha384},
|
||||
{:rsa, :aria_128_gcm, :sha256},
|
||||
{:rsa, :aria_256_gcm, :sha384},
|
||||
{:dh_rsa, :aria_128_gcm, :sha256},
|
||||
{:dh_rsa, :aria_256_gcm, :sha384},
|
||||
{:dh_dss, :aria_128_gcm, :sha256},
|
||||
{:dh_dss, :aria_256_gcm, :sha384},
|
||||
{:dh_anon, :aria_128_gcm, :sha256},
|
||||
{:dh_anon, :aria_256_gcm, :sha384},
|
||||
{:ecdh_ecdsa, :aria_128_gcm, :sha256},
|
||||
{:ecdh_ecdsa, :aria_256_gcm, :sha384},
|
||||
{:ecdh_rsa, :aria_128_gcm, :sha256},
|
||||
{:ecdh_rsa, :aria_256_gcm, :sha384},
|
||||
{:psk, :aria_128_cbc, :sha256},
|
||||
{:psk, :aria_256_cbc, :sha384},
|
||||
{:dhe_psk, :aria_128_cbc, :sha256},
|
||||
{:dhe_psk, :aria_256_cbc, :sha384},
|
||||
{:rsa_psk, :aria_128_cbc, :sha256},
|
||||
{:rsa_psk, :aria_256_cbc, :sha384},
|
||||
{:psk, :aria_128_gcm, :sha256},
|
||||
{:psk, :aria_256_gcm, :sha384},
|
||||
{:rsa_psk, :aria_128_gcm, :sha256},
|
||||
{:rsa_psk, :aria_256_gcm, :sha384},
|
||||
{:ecdhe_psk, :aria_128_cbc, :sha256},
|
||||
{:ecdhe_psk, :aria_256_cbc, :sha384},
|
||||
{:ecdhe_ecdsa, :camellia_128_cbc, :sha256},
|
||||
{:ecdhe_ecdsa, :camellia_256_cbc, :sha384},
|
||||
{:ecdh_ecdsa, :camellia_128_cbc, :sha256},
|
||||
{:ecdh_ecdsa, :camellia_256_cbc, :sha384},
|
||||
{:ecdhe_rsa, :camellia_128_cbc, :sha256},
|
||||
{:ecdhe_rsa, :camellia_256_cbc, :sha384},
|
||||
{:ecdh_rsa, :camellia_128_cbc, :sha256},
|
||||
{:ecdh_rsa, :camellia_256_cbc, :sha384},
|
||||
{:rsa, :camellia_128_gcm, :sha256},
|
||||
{:rsa, :camellia_256_gcm, :sha384},
|
||||
{:dh_rsa, :camellia_128_gcm, :sha256},
|
||||
{:dh_rsa, :camellia_256_gcm, :sha384},
|
||||
{:dh_dss, :camellia_128_gcm, :sha256},
|
||||
{:dh_dss, :camellia_256_gcm, :sha384},
|
||||
{:dh_anon, :camellia_128_gcm, :sha256},
|
||||
{:dh_anon, :camellia_256_gcm, :sha384},
|
||||
{:ecdh_ecdsa, :camellia_128_gcm, :sha256},
|
||||
{:ecdh_ecdsa, :camellia_256_gcm, :sha384},
|
||||
{:ecdh_rsa, :camellia_128_gcm, :sha256},
|
||||
{:ecdh_rsa, :camellia_256_gcm, :sha384},
|
||||
{:psk, :camellia_128_gcm, :sha256},
|
||||
{:psk, :camellia_256_gcm, :sha384},
|
||||
{:rsa_psk, :camellia_128_gcm, :sha256},
|
||||
{:rsa_psk, :camellia_256_gcm, :sha384},
|
||||
{:psk, :camellia_128_cbc, :sha256},
|
||||
{:psk, :camellia_256_cbc, :sha384},
|
||||
{:dhe_psk, :camellia_128_cbc, :sha256},
|
||||
{:dhe_psk, :camellia_256_cbc, :sha384},
|
||||
{:rsa_psk, :camellia_128_cbc, :sha256},
|
||||
{:rsa_psk, :camellia_256_cbc, :sha384},
|
||||
{:ecdhe_psk, :camellia_128_cbc, :sha256},
|
||||
{:ecdhe_psk, :camellia_256_cbc, :sha384},
|
||||
{:rsa, :aes_128, :ccm},
|
||||
{:rsa, :aes_256, :ccm},
|
||||
{:rsa, :aes_128, :ccm_8},
|
||||
{:rsa, :aes_256, :ccm_8},
|
||||
{:psk, :aes_128, :ccm},
|
||||
{:psk, :aes_256, :ccm},
|
||||
{:psk, :aes_128, :ccm_8},
|
||||
{:psk, :aes_256, :ccm_8}
|
||||
])
|
||||
|
||||
@transport_opts [
|
||||
packet: :raw,
|
||||
mode: :binary,
|
||||
active: false
|
||||
]
|
||||
|
||||
@default_versions [:"tlsv1.3", :"tlsv1.2"]
|
||||
@default_timeout 30_000
|
||||
|
||||
Record.defrecordp(
|
||||
:certificate,
|
||||
:Certificate,
|
||||
Record.extract(:Certificate, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
|
||||
)
|
||||
|
||||
Record.defrecordp(
|
||||
:tbs_certificate,
|
||||
:OTPTBSCertificate,
|
||||
Record.extract(:OTPTBSCertificate, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
|
||||
)
|
||||
|
||||
# TODO: Document how to enable revocation checking:
|
||||
# crl_check: true
|
||||
# crl_cache: {:ssl_crl_cache, {:internal, [http: 30_000]}}
|
||||
|
||||
@impl true
|
||||
def connect(address, port, opts) do
|
||||
hostname = Mint.Core.Util.hostname(opts, address)
|
||||
opts = Keyword.delete(opts, :hostname)
|
||||
|
||||
connect(address, hostname, port, opts)
|
||||
end
|
||||
|
||||
defp connect(address, hostname, port, opts) when is_binary(address),
|
||||
do: connect(String.to_charlist(address), hostname, port, opts)
|
||||
|
||||
defp connect(address, hostname, port, opts) do
|
||||
timeout = Keyword.get(opts, :timeout, @default_timeout)
|
||||
inet4? = Keyword.get(opts, :inet4, true)
|
||||
inet6? = Keyword.get(opts, :inet6, false)
|
||||
|
||||
opts = ssl_opts(String.to_charlist(hostname), opts)
|
||||
|
||||
if inet6? do
|
||||
# Try inet6 first, then fall back to the defaults provided by
|
||||
# ssl/gen_tcp if connection fails.
|
||||
case :ssl.connect(address, port, [:inet6 | opts], timeout) do
|
||||
{:ok, sslsocket} ->
|
||||
{:ok, sslsocket}
|
||||
|
||||
_error when inet4? ->
|
||||
wrap_err(:ssl.connect(address, port, opts, timeout))
|
||||
|
||||
error ->
|
||||
wrap_err(error)
|
||||
end
|
||||
else
|
||||
# Use the defaults provided by ssl/gen_tcp.
|
||||
wrap_err(:ssl.connect(address, port, opts, timeout))
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def upgrade(socket, :http, hostname, _port, opts) do
|
||||
hostname = String.to_charlist(hostname)
|
||||
timeout = Keyword.get(opts, :timeout, @default_timeout)
|
||||
|
||||
# Seems like this is not set in :ssl.connect/2 correctly, so set it explicitly
|
||||
Mint.Core.Transport.TCP.setopts(socket, active: false)
|
||||
|
||||
wrap_err(:ssl.connect(socket, ssl_opts(hostname, opts), timeout))
|
||||
end
|
||||
|
||||
def upgrade(_socket, :https, _hostname, _port, _opts) do
|
||||
raise "nested SSL sessions are not supported"
|
||||
end
|
||||
|
||||
@impl true
|
||||
def negotiated_protocol(socket) do
|
||||
wrap_err(:ssl.negotiated_protocol(socket))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def send(socket, payload) do
|
||||
wrap_err(:ssl.send(socket, payload))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def close(socket) do
|
||||
wrap_err(:ssl.close(socket))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def recv(socket, bytes, timeout) do
|
||||
wrap_err(:ssl.recv(socket, bytes, timeout))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def controlling_process(socket, pid) do
|
||||
# We do this dance because it's what gen_tcp does in Erlang. However, ssl
|
||||
# doesn't do this so we need to do it ourselves. Implementation roughly
|
||||
# taken from this:
|
||||
# https://github.com/erlang/otp/blob/fc1f0444e32b039194189af97fb3d5358a2b91e3/lib/kernel/src/inet.erl#L1696-L1754
|
||||
with {:ok, active: active} <- getopts(socket, [:active]),
|
||||
:ok <- setopts(socket, active: false),
|
||||
:ok <- forward_messages_to_new_controlling_process(socket, pid),
|
||||
:ok <- wrap_err(:ssl.controlling_process(socket, pid)) do
|
||||
if(active == :once, do: setopts(socket, active: :once), else: :ok)
|
||||
end
|
||||
end
|
||||
|
||||
defp forward_messages_to_new_controlling_process(socket, pid) do
|
||||
receive do
|
||||
{:ssl, ^socket, _data} = message ->
|
||||
Kernel.send(pid, message)
|
||||
forward_messages_to_new_controlling_process(socket, pid)
|
||||
|
||||
{:ssl_error, ^socket, error} ->
|
||||
{:error, error}
|
||||
|
||||
{:ssl_closed, ^socket} ->
|
||||
{:error, :closed}
|
||||
after
|
||||
0 ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def setopts(socket, opts) do
|
||||
wrap_err(:ssl.setopts(socket, opts))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def getopts(socket, opts) do
|
||||
wrap_err(:ssl.getopts(socket, opts))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def wrap_error(reason) do
|
||||
%Mint.TransportError{reason: reason}
|
||||
end
|
||||
|
||||
defp ssl_opts(hostname, opts) do
|
||||
default_ssl_opts(hostname)
|
||||
|> Keyword.merge(opts)
|
||||
|> Keyword.merge(@transport_opts)
|
||||
|> Keyword.drop([:timeout, :inet4, :inet6])
|
||||
|> add_verify_opts(hostname)
|
||||
|> remove_incompatible_ssl_opts()
|
||||
|> add_ciphers_opt()
|
||||
end
|
||||
|
||||
defp add_verify_opts(opts, hostname) do
|
||||
verify = Keyword.get(opts, :verify)
|
||||
|
||||
if verify == :verify_peer do
|
||||
opts
|
||||
|> add_cacerts()
|
||||
|> add_partial_chain_fun()
|
||||
|> customize_hostname_check(hostname)
|
||||
else
|
||||
opts
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_incompatible_ssl_opts(opts) do
|
||||
# These are the TLS versions that are compatible with :reuse_sessions and :secure_renegotiate
|
||||
# If none of the compatible TLS versions are present in the transport options, then
|
||||
# :reuse_sessions and :secure_renegotiate will be removed from the transport options.
|
||||
compatible_versions = [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
|
||||
versions_opt = Keyword.get(opts, :versions, [])
|
||||
|
||||
if Enum.any?(compatible_versions, &(&1 in versions_opt)) do
|
||||
opts
|
||||
else
|
||||
opts
|
||||
|> Keyword.delete(:reuse_sessions)
|
||||
|> Keyword.delete(:secure_renegotiate)
|
||||
end
|
||||
end
|
||||
|
||||
defp customize_hostname_check(opts, host_or_ip) do
|
||||
if ssl_version() >= [9, 0] do
|
||||
# From OTP 20.0 use built-in support for custom hostname checks
|
||||
add_customize_hostname_check(opts)
|
||||
else
|
||||
# Before OTP 20.0 use mint_shims for hostname check, from a custom
|
||||
# verify_fun
|
||||
add_verify_fun(opts, host_or_ip)
|
||||
end
|
||||
end
|
||||
|
||||
defp add_customize_hostname_check(opts) do
|
||||
Keyword.put_new(opts, :customize_hostname_check, match_fun: &match_fun/2)
|
||||
end
|
||||
|
||||
defp add_verify_fun(opts, host_or_ip) do
|
||||
Keyword.put_new_lazy(opts, :verify_fun, fn ->
|
||||
reference_ids = [dns_id: host_or_ip, ip: host_or_ip]
|
||||
{&verify_fun/3, reference_ids}
|
||||
end)
|
||||
end
|
||||
|
||||
def verify_fun(_, {:bad_cert, _} = reason, _), do: {:fail, reason}
|
||||
def verify_fun(_, {:extension, _}, state), do: {:unknown, state}
|
||||
def verify_fun(_, :valid, state), do: {:valid, state}
|
||||
|
||||
def verify_fun(cert, :valid_peer, state) do
|
||||
if :mint_shims.pkix_verify_hostname(cert, state, match_fun: &match_fun/2) do
|
||||
{:valid, state}
|
||||
else
|
||||
{:fail, {:bad_cert, :hostname_check_failed}}
|
||||
end
|
||||
end
|
||||
|
||||
# Wildcard domain handling for DNS ID entries in the subjectAltName X.509
|
||||
# extension. Note that this is a subset of the wildcard patterns implemented
|
||||
# by OTP when matching against the subject CN attribute, but this is the only
|
||||
# wildcard usage defined by the CA/Browser Forum's Baseline Requirements, and
|
||||
# therefore the only pattern used in commercially issued certificates.
|
||||
defp match_fun({:dns_id, reference}, {:dNSName, [?*, ?. | presented]}) do
|
||||
case domain_without_host(reference) do
|
||||
~c"" -> :default
|
||||
domain -> :string.casefold(domain) == :string.casefold(presented)
|
||||
end
|
||||
end
|
||||
|
||||
# Workaround for a bug that was fixed in OTP 27:
|
||||
# Before OTP 27 when connecting to an IP address and the server offers a
|
||||
# certificate with its IP address in the "subject alternate names" extension,
|
||||
# the TLS handshake fails with a `{:bad_cert, :hostname_check_failed}`.
|
||||
# This clause can be removed once we depend on OTP 27+.
|
||||
defp match_fun({:dns_id, hostname}, {:iPAddress, ip}) do
|
||||
with {:ok, ip_tuple} <- :inet.parse_address(hostname),
|
||||
^ip <- Tuple.to_list(ip_tuple) do
|
||||
true
|
||||
else
|
||||
_ -> :default
|
||||
end
|
||||
end
|
||||
|
||||
defp match_fun(_reference, _presented), do: :default
|
||||
|
||||
defp domain_without_host([]), do: []
|
||||
defp domain_without_host([?. | domain]), do: domain
|
||||
defp domain_without_host([_ | more]), do: domain_without_host(more)
|
||||
|
||||
defp add_ciphers_opt(opts) do
|
||||
Keyword.put_new_lazy(opts, :ciphers, fn ->
|
||||
versions = opts[:versions]
|
||||
get_ciphers_for_versions(versions)
|
||||
end)
|
||||
end
|
||||
|
||||
defp default_ssl_opts(hostname) do
|
||||
# TODO: Add revocation check
|
||||
|
||||
# Note: the :ciphers option is added once the :versions option
|
||||
# has been merged with the user-specified value
|
||||
[
|
||||
server_name_indication: hostname,
|
||||
versions: ssl_versions(),
|
||||
verify: :verify_peer,
|
||||
depth: 4,
|
||||
secure_renegotiate: true,
|
||||
reuse_sessions: true
|
||||
]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def ssl_versions() do
|
||||
available_versions = :ssl.versions()[:available]
|
||||
versions = Enum.filter(@default_versions, &(&1 in available_versions))
|
||||
|
||||
# Remove buggy TLS 1.3 versions
|
||||
if ssl_version() < [10, 0] do
|
||||
versions -- [:"tlsv1.3"]
|
||||
else
|
||||
versions
|
||||
end
|
||||
end
|
||||
|
||||
defp add_cacerts(opts) do
|
||||
if Keyword.has_key?(opts, :cacertfile) or Keyword.has_key?(opts, :cacerts) do
|
||||
opts
|
||||
else
|
||||
try do
|
||||
Keyword.put(opts, :cacerts, :public_key.cacerts_get())
|
||||
rescue
|
||||
_ ->
|
||||
raise_on_missing_castore!()
|
||||
Keyword.put(opts, :cacertfile, CAStore.file_path())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp add_partial_chain_fun(opts) do
|
||||
if Keyword.has_key?(opts, :partial_chain) do
|
||||
opts
|
||||
else
|
||||
case Keyword.fetch(opts, :cacerts) do
|
||||
{:ok, cacerts} ->
|
||||
cacerts = decode_cacerts(cacerts)
|
||||
fun = &partial_chain(cacerts, &1)
|
||||
Keyword.put(opts, :partial_chain, fun)
|
||||
|
||||
:error ->
|
||||
path = Keyword.fetch!(opts, :cacertfile)
|
||||
cacerts = get_cacertfile(path)
|
||||
fun = &partial_chain(cacerts, &1)
|
||||
Keyword.put(opts, :partial_chain, fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_cacertfile(path) do
|
||||
if Application.get_env(:mint, :persistent_term) do
|
||||
case :persistent_term.get({:mint, {:cacertfile, path}}, :error) do
|
||||
{:ok, cacerts} ->
|
||||
cacerts
|
||||
|
||||
:error ->
|
||||
cacerts = decode_cacertfile(path)
|
||||
:persistent_term.put({:mint, {:cacertfile, path}}, {:ok, cacerts})
|
||||
cacerts
|
||||
end
|
||||
else
|
||||
decode_cacertfile(path)
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_cacertfile(path) do
|
||||
path
|
||||
|> File.read!()
|
||||
|> :public_key.pem_decode()
|
||||
|> Enum.filter(&match?({:Certificate, _, :not_encrypted}, &1))
|
||||
|> Enum.map(&:public_key.pem_entry_decode/1)
|
||||
end
|
||||
|
||||
defp decode_cacerts(certs) do
|
||||
Enum.map(certs, fn
|
||||
cert when is_binary(cert) -> :public_key.pkix_decode_cert(cert, :plain)
|
||||
{:cert, _, otp_certificate} -> otp_certificate
|
||||
end)
|
||||
end
|
||||
|
||||
def partial_chain(cacerts, certs) do
|
||||
# TODO: Shim this with OTP 21.1 implementation?
|
||||
|
||||
certs =
|
||||
certs
|
||||
|> Enum.map(&{&1, :public_key.pkix_decode_cert(&1, :plain)})
|
||||
|> Enum.drop_while(&cert_expired?/1)
|
||||
|
||||
trusted =
|
||||
Enum.find_value(certs, fn {der, cert} ->
|
||||
trusted? =
|
||||
Enum.find(cacerts, fn cacert ->
|
||||
extract_public_key_info(cacert) == extract_public_key_info(cert)
|
||||
end)
|
||||
|
||||
if trusted?, do: der
|
||||
end)
|
||||
|
||||
if trusted do
|
||||
{:trusted_ca, trusted}
|
||||
else
|
||||
:unknown_ca
|
||||
end
|
||||
end
|
||||
|
||||
defp cert_expired?({_der, cert}) do
|
||||
now = DateTime.utc_now()
|
||||
{not_before, not_after} = extract_validity(cert)
|
||||
|
||||
DateTime.compare(now, not_before) == :lt or
|
||||
DateTime.compare(now, not_after) == :gt
|
||||
end
|
||||
|
||||
defp extract_validity(cert) do
|
||||
{:Validity, not_before, not_after} =
|
||||
cert
|
||||
|> certificate(:tbsCertificate)
|
||||
|> tbs_certificate(:validity)
|
||||
|
||||
{to_datetime!(not_before), to_datetime!(not_after)}
|
||||
end
|
||||
|
||||
defp extract_public_key_info(cert) do
|
||||
cert
|
||||
|> certificate(:tbsCertificate)
|
||||
|> tbs_certificate(:subjectPublicKeyInfo)
|
||||
end
|
||||
|
||||
defp to_datetime!({:utcTime, time}) do
|
||||
"20#{time}"
|
||||
|> to_datetime!()
|
||||
end
|
||||
|
||||
defp to_datetime!({:generalTime, time}) do
|
||||
time
|
||||
|> to_string()
|
||||
|> to_datetime!()
|
||||
end
|
||||
|
||||
defp to_datetime!(
|
||||
<<year::binary-size(4), month::binary-size(2), day::binary-size(2), hour::binary-size(2),
|
||||
minute::binary-size(2), second::binary-size(2), "Z"::binary>>
|
||||
) do
|
||||
{:ok, datetime, _} =
|
||||
DateTime.from_iso8601("#{year}-#{month}-#{day}T#{hour}:#{minute}:#{second}Z")
|
||||
|
||||
datetime
|
||||
end
|
||||
|
||||
defp blocked_cipher?(%{cipher: cipher, key_exchange: kex, prf: prf}),
|
||||
do: blocked_cipher?({kex, cipher, prf})
|
||||
|
||||
defp blocked_cipher?({kex, cipher, _mac, prf}), do: blocked_cipher?({kex, cipher, prf})
|
||||
defp blocked_cipher?({_kex, _cipher, _prf} = suite), do: suite in @blocked_ciphers
|
||||
|
||||
if Code.ensure_loaded?(CAStore) do
|
||||
defp raise_on_missing_castore! do
|
||||
:ok
|
||||
end
|
||||
else
|
||||
defp raise_on_missing_castore! do
|
||||
raise """
|
||||
default CA trust store not available; please add `:castore` to your project's \
|
||||
dependencies or specify the trust store using the :cacertfile/:cacerts option \
|
||||
within :transport_options. From OTP 25, you can also use:
|
||||
|
||||
* :public_key.cacerts_get/0 to get certificates that you loaded from files or
|
||||
* from the OS with :public_key.cacerts_load/0,1
|
||||
|
||||
See: https://www.erlang.org/blog/my-otp-25-highlights/#ca-certificates-can-be-fetched-from-the-os-standard-place
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
defp wrap_err({:error, reason}), do: {:error, wrap_error(reason)}
|
||||
defp wrap_err(other), do: other
|
||||
|
||||
@doc false
|
||||
def ssl_version() do
|
||||
Application.spec(:ssl, :vsn)
|
||||
|> List.to_string()
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
end
|
||||
|
||||
# Dialyzer warns on :ssl.cipher_suites/1 for now.
|
||||
@dialyzer {:nowarn_function, get_ciphers_for_versions: 1}
|
||||
|
||||
@doc false
|
||||
def get_ciphers_for_versions(versions) do
|
||||
if ssl_version() >= [8, 2, 4] do
|
||||
# :ssl.filter_cipher_suites/2 is available in ssl v8.2.4+
|
||||
versions
|
||||
|> Enum.flat_map(&:ssl.filter_cipher_suites(:ssl.cipher_suites(:all, &1), []))
|
||||
|> Enum.uniq()
|
||||
else
|
||||
:ssl.cipher_suites(:all)
|
||||
end
|
||||
|> Enum.reject(&blocked_cipher?/1)
|
||||
end
|
||||
end
|
||||
92
phoenix/deps/mint/lib/mint/core/transport/tcp.ex
Normal file
92
phoenix/deps/mint/lib/mint/core/transport/tcp.ex
Normal file
@@ -0,0 +1,92 @@
|
||||
defmodule Mint.Core.Transport.TCP do
|
||||
@moduledoc false
|
||||
|
||||
@behaviour Mint.Core.Transport
|
||||
|
||||
@transport_opts [
|
||||
packet: :raw,
|
||||
mode: :binary,
|
||||
active: false
|
||||
]
|
||||
|
||||
@default_timeout 30_000
|
||||
|
||||
@impl true
|
||||
def connect(address, port, opts) when is_binary(address),
|
||||
do: connect(String.to_charlist(address), port, opts)
|
||||
|
||||
def connect(address, port, opts) do
|
||||
opts = Keyword.delete(opts, :hostname)
|
||||
|
||||
timeout = Keyword.get(opts, :timeout, @default_timeout)
|
||||
inet4? = Keyword.get(opts, :inet4, true)
|
||||
inet6? = Keyword.get(opts, :inet6, false)
|
||||
|
||||
opts =
|
||||
opts
|
||||
|> Keyword.merge(@transport_opts)
|
||||
|> Keyword.drop([:alpn_advertised_protocols, :timeout, :inet4, :inet6])
|
||||
|
||||
if inet6? do
|
||||
# Try inet6 first, then fall back to the defaults provided by
|
||||
# gen_tcp if connection fails.
|
||||
case :gen_tcp.connect(address, port, [:inet6 | opts], timeout) do
|
||||
{:ok, socket} ->
|
||||
{:ok, socket}
|
||||
|
||||
_error when inet4? ->
|
||||
wrap_err(:gen_tcp.connect(address, port, opts, timeout))
|
||||
|
||||
error ->
|
||||
wrap_err(error)
|
||||
end
|
||||
else
|
||||
# Use the defaults provided by gen_tcp.
|
||||
wrap_err(:gen_tcp.connect(address, port, opts, timeout))
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def upgrade(socket, _scheme, _hostname, _port, _opts) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def negotiated_protocol(_socket), do: wrap_err({:error, :protocol_not_negotiated})
|
||||
|
||||
@impl true
|
||||
def send(socket, payload) do
|
||||
wrap_err(:gen_tcp.send(socket, payload))
|
||||
end
|
||||
|
||||
@impl true
|
||||
defdelegate close(socket), to: :gen_tcp
|
||||
|
||||
@impl true
|
||||
def recv(socket, bytes, timeout) do
|
||||
wrap_err(:gen_tcp.recv(socket, bytes, timeout))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def controlling_process(socket, pid) do
|
||||
wrap_err(:gen_tcp.controlling_process(socket, pid))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def setopts(socket, opts) do
|
||||
wrap_err(:inet.setopts(socket, opts))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def getopts(socket, opts) do
|
||||
wrap_err(:inet.getopts(socket, opts))
|
||||
end
|
||||
|
||||
@impl true
|
||||
def wrap_error(reason) do
|
||||
%Mint.TransportError{reason: reason}
|
||||
end
|
||||
|
||||
defp wrap_err({:error, reason}), do: {:error, wrap_error(reason)}
|
||||
defp wrap_err(other), do: other
|
||||
end
|
||||
71
phoenix/deps/mint/lib/mint/core/util.ex
Normal file
71
phoenix/deps/mint/lib/mint/core/util.ex
Normal file
@@ -0,0 +1,71 @@
|
||||
defmodule Mint.Core.Util do
|
||||
@moduledoc false
|
||||
|
||||
alias Mint.Types
|
||||
|
||||
@spec hostname(keyword(), String.t()) :: String.t()
|
||||
def hostname(opts, address) when is_list(opts) do
|
||||
case Keyword.fetch(opts, :hostname) do
|
||||
{:ok, hostname} ->
|
||||
hostname
|
||||
|
||||
:error when is_binary(address) ->
|
||||
address
|
||||
|
||||
:error ->
|
||||
raise ArgumentError, "the :hostname option is required when address is not a binary"
|
||||
end
|
||||
end
|
||||
|
||||
@spec inet_opts(:gen_tcp | :ssl, :gen_tcp.socket() | :ssl.sslsocket()) :: :ok | {:error, term()}
|
||||
def inet_opts(transport, socket) do
|
||||
with {:ok, opts} <- transport.getopts(socket, [:sndbuf, :recbuf, :buffer]),
|
||||
buffer = calculate_buffer(opts),
|
||||
:ok <- transport.setopts(socket, buffer: buffer) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec scheme_to_transport(atom()) :: module()
|
||||
def scheme_to_transport(:http), do: Mint.Core.Transport.TCP
|
||||
def scheme_to_transport(:https), do: Mint.Core.Transport.SSL
|
||||
def scheme_to_transport(module) when is_atom(module), do: module
|
||||
|
||||
defp calculate_buffer(opts) do
|
||||
Keyword.fetch!(opts, :buffer)
|
||||
|> max(Keyword.fetch!(opts, :sndbuf))
|
||||
|> max(Keyword.fetch!(opts, :recbuf))
|
||||
end
|
||||
|
||||
# Adds a header to the list of headers unless it's nil or it's already there.
|
||||
@spec put_new_header(Types.headers(), String.t(), String.t() | nil) :: Types.headers()
|
||||
def put_new_header(headers, name, value)
|
||||
|
||||
def put_new_header(headers, _name, nil) do
|
||||
headers
|
||||
end
|
||||
|
||||
def put_new_header(headers, name, value) do
|
||||
if List.keymember?(headers, name, 0) do
|
||||
headers
|
||||
else
|
||||
[{name, value} | headers]
|
||||
end
|
||||
end
|
||||
|
||||
@spec put_new_header_lazy(Types.headers(), String.t(), (-> String.t())) :: Types.headers()
|
||||
def put_new_header_lazy(headers, name, fun) do
|
||||
if List.keymember?(headers, name, 0) do
|
||||
headers
|
||||
else
|
||||
[{name, fun.()} | headers]
|
||||
end
|
||||
end
|
||||
|
||||
# If the buffer is empty, reusing the incoming data saves
|
||||
# a potentially large allocation of memory.
|
||||
# This should be fixed in a subsequent OTP release.
|
||||
@spec maybe_concat(binary(), binary()) :: binary()
|
||||
def maybe_concat(<<>>, data), do: data
|
||||
def maybe_concat(buffer, data) when is_binary(buffer), do: buffer <> data
|
||||
end
|
||||
1067
phoenix/deps/mint/lib/mint/http.ex
Normal file
1067
phoenix/deps/mint/lib/mint/http.ex
Normal file
File diff suppressed because it is too large
Load Diff
1157
phoenix/deps/mint/lib/mint/http1.ex
Normal file
1157
phoenix/deps/mint/lib/mint/http1.ex
Normal file
File diff suppressed because it is too large
Load Diff
72
phoenix/deps/mint/lib/mint/http1/parse.ex
Normal file
72
phoenix/deps/mint/lib/mint/http1/parse.ex
Normal file
@@ -0,0 +1,72 @@
|
||||
defmodule Mint.HTTP1.Parse do
|
||||
@moduledoc false
|
||||
|
||||
defmacro is_digit(char), do: quote(do: unquote(char) in ?0..?9)
|
||||
defmacro is_alpha(char), do: quote(do: unquote(char) in ?a..?z or unquote(char) in ?A..?Z)
|
||||
defmacro is_whitespace(char), do: quote(do: unquote(char) in ~c"\s\t")
|
||||
defmacro is_comma(char), do: quote(do: unquote(char) == ?,)
|
||||
defmacro is_vchar(char), do: quote(do: unquote(char) in 33..126)
|
||||
|
||||
defmacro is_tchar(char) do
|
||||
quote do
|
||||
is_digit(unquote(char)) or is_alpha(unquote(char)) or unquote(char) in ~c"!#$%&'*+-.^_`|~"
|
||||
end
|
||||
end
|
||||
|
||||
def ignore_until_crlf(<<>>), do: :more
|
||||
def ignore_until_crlf(<<"\r\n", rest::binary>>), do: {:ok, rest}
|
||||
def ignore_until_crlf(<<_char, rest::binary>>), do: ignore_until_crlf(rest)
|
||||
|
||||
def content_length_header(string) do
|
||||
case Integer.parse(String.trim_trailing(string)) do
|
||||
{length, ""} when length >= 0 -> {:ok, length}
|
||||
_other -> {:error, {:invalid_content_length_header, string}}
|
||||
end
|
||||
end
|
||||
|
||||
def connection_header(string) do
|
||||
split_into_downcase_tokens(string)
|
||||
end
|
||||
|
||||
def transfer_encoding_header(string) do
|
||||
split_into_downcase_tokens(string)
|
||||
end
|
||||
|
||||
defp split_into_downcase_tokens(string) do
|
||||
case token_list_downcase(string) do
|
||||
{:ok, []} -> {:error, :empty_token_list}
|
||||
{:ok, list} -> {:ok, list}
|
||||
:error -> {:error, {:invalid_token_list, string}}
|
||||
end
|
||||
end
|
||||
|
||||
# Made public for testing.
|
||||
def token_list_downcase(string), do: token_list_downcase(string, [])
|
||||
|
||||
defp token_list_downcase(<<>>, acc), do: {:ok, :lists.reverse(acc)}
|
||||
|
||||
# Skip all whitespace and commas.
|
||||
defp token_list_downcase(<<char, rest::binary>>, acc)
|
||||
when is_whitespace(char) or is_comma(char),
|
||||
do: token_list_downcase(rest, acc)
|
||||
|
||||
defp token_list_downcase(rest, acc), do: token_downcase(rest, _token_acc = <<>>, acc)
|
||||
|
||||
defp token_downcase(<<char, rest::binary>>, token_acc, acc) when is_tchar(char),
|
||||
do: token_downcase(rest, <<token_acc::binary, downcase_ascii_char(char)>>, acc)
|
||||
|
||||
defp token_downcase(rest, token_acc, acc), do: token_list_sep_downcase(rest, [token_acc | acc])
|
||||
|
||||
defp token_list_sep_downcase(<<>>, acc), do: {:ok, :lists.reverse(acc)}
|
||||
|
||||
defp token_list_sep_downcase(<<char, rest::binary>>, acc) when is_whitespace(char),
|
||||
do: token_list_sep_downcase(rest, acc)
|
||||
|
||||
defp token_list_sep_downcase(<<char, rest::binary>>, acc) when is_comma(char),
|
||||
do: token_list_downcase(rest, acc)
|
||||
|
||||
defp token_list_sep_downcase(_rest, _acc), do: :error
|
||||
|
||||
defp downcase_ascii_char(char) when char in ?A..?Z, do: char + 32
|
||||
defp downcase_ascii_char(char) when char in 0..127, do: char
|
||||
end
|
||||
69
phoenix/deps/mint/lib/mint/http1/request.ex
Normal file
69
phoenix/deps/mint/lib/mint/http1/request.ex
Normal file
@@ -0,0 +1,69 @@
|
||||
defmodule Mint.HTTP1.Request do
|
||||
@moduledoc false
|
||||
|
||||
import Mint.HTTP1.Parse
|
||||
|
||||
def encode(method, target, headers, body) do
|
||||
body = [
|
||||
encode_request_line(method, target),
|
||||
encode_headers(headers),
|
||||
"\r\n",
|
||||
encode_body(body)
|
||||
]
|
||||
|
||||
{:ok, body}
|
||||
catch
|
||||
{:mint, reason} -> {:error, reason}
|
||||
end
|
||||
|
||||
defp encode_request_line(method, target) do
|
||||
[method, ?\s, target, " HTTP/1.1\r\n"]
|
||||
end
|
||||
|
||||
defp encode_headers(headers) do
|
||||
Enum.reduce(headers, "", fn {name, value}, acc ->
|
||||
validate_header_name!(name)
|
||||
validate_header_value!(name, value)
|
||||
[acc, name, ": ", value, "\r\n"]
|
||||
end)
|
||||
end
|
||||
|
||||
defp encode_body(nil), do: ""
|
||||
defp encode_body(:stream), do: ""
|
||||
defp encode_body(body), do: body
|
||||
|
||||
def encode_chunk(:eof) do
|
||||
"0\r\n\r\n"
|
||||
end
|
||||
|
||||
def encode_chunk({:eof, trailing_headers}) do
|
||||
["0\r\n", encode_headers(trailing_headers), "\r\n"]
|
||||
end
|
||||
|
||||
def encode_chunk(chunk) do
|
||||
length = IO.iodata_length(chunk)
|
||||
[Integer.to_string(length, 16), "\r\n", chunk, "\r\n"]
|
||||
end
|
||||
|
||||
defp validate_header_name!(name) do
|
||||
_ =
|
||||
for <<char <- name>> do
|
||||
unless is_tchar(char) do
|
||||
throw({:mint, {:invalid_header_name, name}})
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp validate_header_value!(name, value) do
|
||||
_ =
|
||||
for <<char <- value>> do
|
||||
unless is_vchar(char) or char in ~c"\s\t" do
|
||||
throw({:mint, {:invalid_header_value, name, value}})
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
43
phoenix/deps/mint/lib/mint/http1/response.ex
Normal file
43
phoenix/deps/mint/lib/mint/http1/response.ex
Normal file
@@ -0,0 +1,43 @@
|
||||
defmodule Mint.HTTP1.Response do
|
||||
@moduledoc false
|
||||
|
||||
alias Mint.Core.Headers
|
||||
|
||||
def decode_status_line(binary) do
|
||||
case :erlang.decode_packet(:http_bin, binary, []) do
|
||||
{:ok, {:http_response, version, status, reason}, rest} ->
|
||||
{:ok, {version, status, reason}, rest}
|
||||
|
||||
{:ok, _other, _rest} ->
|
||||
:error
|
||||
|
||||
{:more, _length} ->
|
||||
:more
|
||||
|
||||
{:error, _reason} ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def decode_header(binary) do
|
||||
case :erlang.decode_packet(:httph_bin, binary, []) do
|
||||
{:ok, {:http_header, _unused, name, _reserved, value}, rest} ->
|
||||
{:ok, {header_name(name), value}, rest}
|
||||
|
||||
{:ok, :http_eoh, rest} ->
|
||||
{:ok, :eof, rest}
|
||||
|
||||
{:ok, _other, _rest} ->
|
||||
:error
|
||||
|
||||
{:more, _length} ->
|
||||
:more
|
||||
|
||||
{:error, _reason} ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp header_name(atom) when is_atom(atom), do: atom |> Atom.to_string() |> header_name()
|
||||
defp header_name(binary) when is_binary(binary), do: Headers.lower_raw(binary)
|
||||
end
|
||||
2266
phoenix/deps/mint/lib/mint/http2.ex
Normal file
2266
phoenix/deps/mint/lib/mint/http2.ex
Normal file
File diff suppressed because it is too large
Load Diff
478
phoenix/deps/mint/lib/mint/http2/frame.ex
Normal file
478
phoenix/deps/mint/lib/mint/http2/frame.ex
Normal file
@@ -0,0 +1,478 @@
|
||||
defmodule Mint.HTTP2.Frame do
|
||||
@moduledoc false
|
||||
|
||||
import Bitwise, only: [band: 2, bor: 2]
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Record
|
||||
|
||||
shared_stream = [:stream_id, {:flags, 0x00}]
|
||||
shared_conn = [stream_id: 0, flags: 0x00]
|
||||
|
||||
defrecord :data, shared_stream ++ [:data, :padding]
|
||||
defrecord :headers, shared_stream ++ [:exclusive?, :stream_dependency, :weight, :hbf, :padding]
|
||||
defrecord :priority, shared_stream ++ [:exclusive?, :stream_dependency, :weight]
|
||||
defrecord :rst_stream, shared_stream ++ [:error_code]
|
||||
defrecord :settings, shared_conn ++ [:params]
|
||||
defrecord :push_promise, shared_stream ++ [:promised_stream_id, :hbf, :padding]
|
||||
defrecord :ping, shared_conn ++ [:opaque_data]
|
||||
defrecord :goaway, shared_conn ++ [:last_stream_id, :error_code, :debug_data]
|
||||
defrecord :window_update, shared_stream ++ [:window_size_increment]
|
||||
defrecord :continuation, shared_stream ++ [:hbf]
|
||||
defrecord :unknown, []
|
||||
|
||||
@types %{
|
||||
data: 0x00,
|
||||
headers: 0x01,
|
||||
priority: 0x02,
|
||||
rst_stream: 0x03,
|
||||
settings: 0x04,
|
||||
push_promise: 0x05,
|
||||
ping: 0x06,
|
||||
goaway: 0x07,
|
||||
window_update: 0x08,
|
||||
continuation: 0x09
|
||||
}
|
||||
|
||||
## Inspecting
|
||||
|
||||
@spec inspect(tuple()) :: String.t()
|
||||
|
||||
for {type, _code} <- @types do
|
||||
def inspect(frame) when is_record(frame, unquote(type)) do
|
||||
unquote(String.upcase(Atom.to_string(type))) <> Kernel.inspect(unquote(type)(frame))
|
||||
end
|
||||
end
|
||||
|
||||
## Flag handling
|
||||
|
||||
@flags %{
|
||||
data: [end_stream: 0x01, padded: 0x08],
|
||||
headers: [end_stream: 0x01, end_headers: 0x04, padded: 0x08, priority: 0x20],
|
||||
settings: [ack: 0x01],
|
||||
push_promise: [end_headers: 0x04, padded: 0x08],
|
||||
ping: [ack: 0x01],
|
||||
continuation: [end_headers: 0x04]
|
||||
}
|
||||
|
||||
@spec set_flags(byte(), atom(), [flag_name :: atom()]) :: byte()
|
||||
def set_flags(initial_flags \\ 0x00, frame_name, flags_to_set)
|
||||
when is_integer(initial_flags) and is_list(flags_to_set) do
|
||||
Enum.reduce(flags_to_set, initial_flags, &set_flag(&2, frame_name, &1))
|
||||
end
|
||||
|
||||
@spec flag_set?(byte(), atom(), atom()) :: boolean()
|
||||
def flag_set?(flags, frame, flag_name)
|
||||
|
||||
for {frame, flags} <- @flags,
|
||||
{flag_name, flag_value} <- flags do
|
||||
defp set_flag(flags, unquote(frame), unquote(flag_name)), do: bor(flags, unquote(flag_value))
|
||||
|
||||
def flag_set?(flags, unquote(frame), unquote(flag_name)),
|
||||
do: band(flags, unquote(flag_value)) == unquote(flag_value)
|
||||
end
|
||||
|
||||
defmacrop is_flag_set(flags, flag) do
|
||||
quote do
|
||||
band(unquote(flags), unquote(flag)) == unquote(flag)
|
||||
end
|
||||
end
|
||||
|
||||
## Parsing
|
||||
|
||||
@doc """
|
||||
Decodes the next frame of the given binary.
|
||||
|
||||
Returns `{:ok, frame, rest}` if successful, `{:error, reason}` if not.
|
||||
"""
|
||||
@spec decode_next(binary()) :: {:ok, tuple(), binary()} | :more | {:error, reason}
|
||||
when reason:
|
||||
{:frame_size_error, atom()}
|
||||
| {:protocol_error, binary()}
|
||||
| :payload_too_big
|
||||
def decode_next(bin, max_frame_size \\ 16_384) when is_binary(bin) do
|
||||
case decode_next_raw(bin) do
|
||||
{:ok, {_type, _flags, _stream_id, payload}, _rest}
|
||||
when byte_size(payload) > max_frame_size ->
|
||||
{:error, :payload_too_big}
|
||||
|
||||
{:ok, {type, flags, stream_id, payload}, rest} ->
|
||||
{:ok, decode_contents(type, flags, stream_id, payload), rest}
|
||||
|
||||
:more ->
|
||||
:more
|
||||
end
|
||||
catch
|
||||
:throw, {:mint, reason} -> {:error, reason}
|
||||
end
|
||||
|
||||
defp decode_next_raw(<<
|
||||
length::24,
|
||||
type,
|
||||
flags,
|
||||
_reserved::1,
|
||||
stream_id::31,
|
||||
payload::size(length)-binary,
|
||||
rest::binary
|
||||
>>) do
|
||||
{:ok, {type, flags, stream_id, payload}, rest}
|
||||
end
|
||||
|
||||
defp decode_next_raw(_other) do
|
||||
:more
|
||||
end
|
||||
|
||||
for {frame, type} <- @types do
|
||||
function = :"decode_#{frame}"
|
||||
|
||||
defp decode_contents(unquote(type), flags, stream_id, payload) do
|
||||
unquote(function)(flags, stream_id, payload)
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_contents(_type, _flags, _stream_id, _payload) do
|
||||
unknown()
|
||||
end
|
||||
|
||||
# Parsing of specific frames
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.1
|
||||
defp decode_data(flags, stream_id, payload) do
|
||||
{data, padding} = decode_padding(:data, flags, payload)
|
||||
data(stream_id: stream_id, flags: flags, data: data, padding: padding)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.2
|
||||
defp decode_headers(flags, stream_id, payload) do
|
||||
{data, padding} = decode_padding(:headers, flags, payload)
|
||||
|
||||
{exclusive?, stream_dependency, weight, data} =
|
||||
if flag_set?(flags, :headers, :priority) do
|
||||
<<exclusive::1, stream_dependency::31, weight::8, rest::binary>> = data
|
||||
{exclusive == 1, stream_dependency, weight + 1, rest}
|
||||
else
|
||||
{nil, nil, nil, data}
|
||||
end
|
||||
|
||||
headers(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
padding: padding,
|
||||
exclusive?: exclusive?,
|
||||
stream_dependency: stream_dependency,
|
||||
weight: weight,
|
||||
hbf: data
|
||||
)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.3
|
||||
defp decode_priority(_flags, _stream_id, payload) when byte_size(payload) != 5 do
|
||||
throw({:mint, {:frame_size_error, :priority}})
|
||||
end
|
||||
|
||||
defp decode_priority(flags, stream_id, payload) do
|
||||
<<exclusive::1, stream_dependency::31, weight::8>> = payload
|
||||
|
||||
priority(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
exclusive?: exclusive == 1,
|
||||
stream_dependency: stream_dependency,
|
||||
weight: weight + 1
|
||||
)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.4
|
||||
defp decode_rst_stream(_flags, _stream_id, payload) when byte_size(payload) != 4 do
|
||||
throw({:mint, {:frame_size_error, :rst_stream}})
|
||||
end
|
||||
|
||||
defp decode_rst_stream(flags, stream_id, <<error_code::32>>) do
|
||||
rst_stream(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
error_code: humanize_error_code(error_code)
|
||||
)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.5
|
||||
defp decode_settings(_flags, _stream_id, payload) when rem(byte_size(payload), 6) != 0 do
|
||||
throw({:mint, {:frame_size_error, :settings}})
|
||||
end
|
||||
|
||||
defp decode_settings(flags, stream_id, payload) do
|
||||
settings(stream_id: stream_id, flags: flags, params: decode_settings_params(payload))
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.6
|
||||
defp decode_push_promise(flags, stream_id, payload) do
|
||||
{data, padding} = decode_padding(:push_promise, flags, payload)
|
||||
<<_reserved::1, promised_stream_id::31, header_block_fragment::binary>> = data
|
||||
|
||||
push_promise(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
promised_stream_id: promised_stream_id,
|
||||
hbf: header_block_fragment,
|
||||
padding: padding
|
||||
)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.7
|
||||
defp decode_ping(_flags, _stream_id, payload) when byte_size(payload) != 8 do
|
||||
throw({:mint, {:frame_size_error, :ping}})
|
||||
end
|
||||
|
||||
defp decode_ping(flags, stream_id, payload) do
|
||||
ping(stream_id: stream_id, flags: flags, opaque_data: payload)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.8
|
||||
defp decode_goaway(flags, stream_id, payload) do
|
||||
<<_reserved::1, last_stream_id::31, error_code::32, debug_data::binary>> = payload
|
||||
|
||||
goaway(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
last_stream_id: last_stream_id,
|
||||
error_code: humanize_error_code(error_code),
|
||||
debug_data: debug_data
|
||||
)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.9
|
||||
defp decode_window_update(_flags, _stream_id, payload) when byte_size(payload) != 4 do
|
||||
throw({:mint, {:frame_size_error, :window_update}})
|
||||
end
|
||||
|
||||
defp decode_window_update(_flags, _stream_id, <<_reserved::1, 0::31>>) do
|
||||
throw({:mint, {:protocol_error, "bad WINDOW_SIZE increment"}})
|
||||
end
|
||||
|
||||
defp decode_window_update(flags, stream_id, <<_reserved::1, window_size_increment::31>>) do
|
||||
window_update(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
window_size_increment: window_size_increment
|
||||
)
|
||||
end
|
||||
|
||||
# http://httpwg.org/specs/rfc7540.html#rfc.section.6.10
|
||||
defp decode_continuation(flags, stream_id, payload) do
|
||||
continuation(stream_id: stream_id, flags: flags, hbf: payload)
|
||||
end
|
||||
|
||||
defp decode_padding(frame, flags, <<pad_length, rest::binary>> = payload)
|
||||
when is_flag_set(flags, unquote(@flags[:data][:padded])) do
|
||||
if pad_length >= byte_size(payload) do
|
||||
debug_data =
|
||||
"the padding length of a #{Kernel.inspect(frame)} frame is bigger than the payload length"
|
||||
|
||||
throw({:mint, {:protocol_error, debug_data}})
|
||||
else
|
||||
# 1 byte is for the space taken by pad_length
|
||||
data_length = byte_size(payload) - pad_length - 1
|
||||
<<data::size(data_length)-binary, padding::size(pad_length)-binary>> = rest
|
||||
{data, padding}
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_padding(_frame, _flags, payload) do
|
||||
{payload, nil}
|
||||
end
|
||||
|
||||
defp decode_settings_params(payload) do
|
||||
decode_settings_params(payload, _acc = [])
|
||||
end
|
||||
|
||||
defp decode_settings_params(<<>>, acc) do
|
||||
Enum.reverse(acc)
|
||||
end
|
||||
|
||||
defp decode_settings_params(<<identifier::16, value::32, rest::binary>>, acc) do
|
||||
# From http://httpwg.org/specs/rfc7540.html#SettingValues:
|
||||
# An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST
|
||||
# ignore that setting.
|
||||
acc =
|
||||
case identifier do
|
||||
0x01 -> [{:header_table_size, value} | acc]
|
||||
0x02 -> [{:enable_push, value == 1} | acc]
|
||||
0x03 -> [{:max_concurrent_streams, value} | acc]
|
||||
0x04 -> [{:initial_window_size, value} | acc]
|
||||
0x05 -> [{:max_frame_size, value} | acc]
|
||||
0x06 -> [{:max_header_list_size, value} | acc]
|
||||
0x08 -> [{:enable_connect_protocol, value == 1} | acc]
|
||||
_other -> acc
|
||||
end
|
||||
|
||||
decode_settings_params(rest, acc)
|
||||
end
|
||||
|
||||
## Encoding
|
||||
|
||||
@doc """
|
||||
Encodes the given `frame`.
|
||||
"""
|
||||
@spec encode(tuple()) :: iodata()
|
||||
def encode(frame)
|
||||
|
||||
def encode(data(stream_id: stream_id, flags: flags, data: data, padding: nil)) do
|
||||
encode_raw(@types[:data], flags, stream_id, data)
|
||||
end
|
||||
|
||||
def encode(data(stream_id: stream_id, flags: flags, data: data, padding: padding)) do
|
||||
flags = set_flags(flags, :data, [:padded])
|
||||
payload = [byte_size(padding), data, padding]
|
||||
encode_raw(@types[:data], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(headers() = frame) do
|
||||
headers(
|
||||
flags: flags,
|
||||
stream_id: stream_id,
|
||||
exclusive?: exclusive?,
|
||||
stream_dependency: stream_dependency,
|
||||
weight: weight,
|
||||
hbf: hbf,
|
||||
padding: padding
|
||||
) = frame
|
||||
|
||||
payload = hbf
|
||||
|
||||
{payload, flags} =
|
||||
if stream_dependency && weight && is_boolean(exclusive?) do
|
||||
{
|
||||
[<<if(exclusive?, do: 1, else: 0)::1, stream_dependency::31>>, weight - 1, payload],
|
||||
set_flags(flags, :headers, [:priority])
|
||||
}
|
||||
else
|
||||
{payload, flags}
|
||||
end
|
||||
|
||||
{payload, flags} =
|
||||
if padding do
|
||||
{[byte_size(padding), payload, padding], set_flags(flags, :headers, [:padded])}
|
||||
else
|
||||
{payload, flags}
|
||||
end
|
||||
|
||||
encode_raw(@types[:headers], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(priority() = frame) do
|
||||
priority(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
exclusive?: exclusive?,
|
||||
stream_dependency: stream_dependency,
|
||||
weight: weight
|
||||
) = frame
|
||||
|
||||
payload = [
|
||||
<<if(exclusive?, do: 1, else: 0)::1, stream_dependency::31>>,
|
||||
weight - 1
|
||||
]
|
||||
|
||||
encode_raw(@types[:priority], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(rst_stream(stream_id: stream_id, flags: flags, error_code: error_code)) do
|
||||
payload = <<dehumanize_error_code(error_code)::32>>
|
||||
encode_raw(@types[:rst_stream], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(settings(stream_id: stream_id, flags: flags, params: params)) do
|
||||
payload =
|
||||
Enum.map(params, fn
|
||||
{:header_table_size, value} -> <<0x01::16, value::32>>
|
||||
{:enable_push, value} -> <<0x02::16, if(value, do: 1, else: 0)::32>>
|
||||
{:max_concurrent_streams, value} -> <<0x03::16, value::32>>
|
||||
{:initial_window_size, value} -> <<0x04::16, value::32>>
|
||||
{:max_frame_size, value} -> <<0x05::16, value::32>>
|
||||
{:max_header_list_size, value} -> <<0x06::16, value::32>>
|
||||
{:enable_connect_protocol, value} -> <<0x08::16, if(value, do: 1, else: 0)::32>>
|
||||
end)
|
||||
|
||||
encode_raw(@types[:settings], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(push_promise() = frame) do
|
||||
push_promise(
|
||||
stream_id: stream_id,
|
||||
flags: flags,
|
||||
promised_stream_id: promised_stream_id,
|
||||
hbf: hbf,
|
||||
padding: padding
|
||||
) = frame
|
||||
|
||||
payload = [<<0::1, promised_stream_id::31>>, hbf]
|
||||
|
||||
{payload, flags} =
|
||||
if padding do
|
||||
{
|
||||
[byte_size(padding), payload, padding],
|
||||
set_flags(flags, :push_promise, [:padded])
|
||||
}
|
||||
else
|
||||
{payload, flags}
|
||||
end
|
||||
|
||||
encode_raw(@types[:push_promise], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(ping(stream_id: 0, flags: flags, opaque_data: opaque_data)) do
|
||||
encode_raw(@types[:ping], flags, 0, opaque_data)
|
||||
end
|
||||
|
||||
def encode(goaway() = frame) do
|
||||
goaway(
|
||||
stream_id: 0,
|
||||
flags: flags,
|
||||
last_stream_id: last_stream_id,
|
||||
error_code: error_code,
|
||||
debug_data: debug_data
|
||||
) = frame
|
||||
|
||||
payload = [<<0::1, last_stream_id::31, dehumanize_error_code(error_code)::32>>, debug_data]
|
||||
encode_raw(@types[:goaway], flags, 0, payload)
|
||||
end
|
||||
|
||||
def encode(window_update(stream_id: stream_id, flags: flags, window_size_increment: wsi)) do
|
||||
payload = <<0::1, wsi::31>>
|
||||
encode_raw(@types[:window_update], flags, stream_id, payload)
|
||||
end
|
||||
|
||||
def encode(continuation(stream_id: stream_id, flags: flags, hbf: hbf)) do
|
||||
encode_raw(@types[:continuation], flags, stream_id, _payload = hbf)
|
||||
end
|
||||
|
||||
def encode_raw(type, flags, stream_id, payload) do
|
||||
[<<IO.iodata_length(payload)::24>>, type, flags, <<0::1, stream_id::31>>, payload]
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
error_codes = %{
|
||||
0x00 => :no_error,
|
||||
0x01 => :protocol_error,
|
||||
0x02 => :internal_error,
|
||||
0x03 => :flow_control_error,
|
||||
0x04 => :settings_timeout,
|
||||
0x05 => :stream_closed,
|
||||
0x06 => :frame_size_error,
|
||||
0x07 => :refused_stream,
|
||||
0x08 => :cancel,
|
||||
0x09 => :compression_error,
|
||||
0x0A => :connect_error,
|
||||
0x0B => :enhance_your_calm,
|
||||
0x0C => :inadequate_security,
|
||||
0x0D => :http_1_1_required
|
||||
}
|
||||
|
||||
for {code, human_code} <- error_codes do
|
||||
defp humanize_error_code(unquote(code)), do: unquote(human_code)
|
||||
defp dehumanize_error_code(unquote(human_code)), do: unquote(code)
|
||||
end
|
||||
|
||||
defp humanize_error_code(code), do: {:custom_error, code}
|
||||
defp dehumanize_error_code({:custom_error, code}), do: code
|
||||
end
|
||||
65
phoenix/deps/mint/lib/mint/http_error.ex
Normal file
65
phoenix/deps/mint/lib/mint/http_error.ex
Normal file
@@ -0,0 +1,65 @@
|
||||
defmodule Mint.HTTPError do
|
||||
@moduledoc """
|
||||
An HTTP error.
|
||||
|
||||
This exception struct is used to represent HTTP errors of all sorts and for
|
||||
both HTTP/1 and HTTP/2.
|
||||
|
||||
A `Mint.HTTPError` struct is an exception, so it can be raised as any
|
||||
other exception.
|
||||
|
||||
## Struct
|
||||
|
||||
The `Mint.HTTPError` struct is opaque, that is, not all of its fields are public.
|
||||
The list of public fields is:
|
||||
|
||||
* `:reason` - the error reason. Can be one of:
|
||||
|
||||
* a term of type `t:Mint.HTTP1.error_reason/0`. See its documentation for
|
||||
more information.
|
||||
|
||||
* a term of type `t:Mint.HTTP2.error_reason/0`. See its documentation for
|
||||
more information.
|
||||
|
||||
* `{:proxy, reason}`, which is used when an HTTP error happens when connecting
|
||||
to a tunnel proxy. `reason` can be:
|
||||
|
||||
* `:tunnel_timeout` - when the tunnel times out.
|
||||
|
||||
* `{:unexpected_status, status}` - when the proxy returns an unexpected
|
||||
status `status`.
|
||||
|
||||
* `{:unexpected_trailing_responses, responses}` - when the proxy returns
|
||||
unexpected responses (`responses`).
|
||||
|
||||
## Message representation
|
||||
|
||||
If you want to convert an error reason to a human-friendly message (for example
|
||||
for using in logs), you can use `Exception.message/1`:
|
||||
|
||||
iex> {:error, %Mint.HTTPError{} = error} = Mint.HTTP.connect(:http, "bad-response.com", 80)
|
||||
iex> Exception.message(error)
|
||||
"the response contains two or more Content-Length headers"
|
||||
|
||||
"""
|
||||
|
||||
alias Mint.{HTTP1, HTTP2}
|
||||
|
||||
@type proxy_reason() ::
|
||||
{:proxy,
|
||||
HTTP1.error_reason()
|
||||
| HTTP2.error_reason()
|
||||
| :tunnel_timeout
|
||||
| {:unexpected_status, non_neg_integer()}
|
||||
| {:unexpected_trailing_responses, list()}}
|
||||
|
||||
@type t() :: %__MODULE__{
|
||||
reason: HTTP1.error_reason() | HTTP2.error_reason() | proxy_reason() | term()
|
||||
}
|
||||
|
||||
defexception [:reason, :module]
|
||||
|
||||
def message(%__MODULE__{reason: reason, module: module}) do
|
||||
module.format_error(reason)
|
||||
end
|
||||
end
|
||||
146
phoenix/deps/mint/lib/mint/negotiate.ex
Normal file
146
phoenix/deps/mint/lib/mint/negotiate.ex
Normal file
@@ -0,0 +1,146 @@
|
||||
defmodule Mint.Negotiate do
|
||||
@moduledoc false
|
||||
|
||||
alias Mint.{
|
||||
HTTP1,
|
||||
HTTP2,
|
||||
TransportError,
|
||||
Types
|
||||
}
|
||||
|
||||
alias Mint.Core.Util
|
||||
|
||||
@default_protocols [:http1, :http2]
|
||||
@transport_opts [alpn_advertised_protocols: ["http/1.1", "h2"]]
|
||||
|
||||
@spec connect(Types.scheme(), Types.address(), :inet.port_number(), keyword()) ::
|
||||
{:ok, Mint.HTTP.t()} | {:error, Types.error()}
|
||||
def connect(scheme, address, port, opts \\ []) do
|
||||
{protocols, opts} = Keyword.pop(opts, :protocols, @default_protocols)
|
||||
|
||||
case Enum.sort(protocols) do
|
||||
[:http1] ->
|
||||
HTTP1.connect(scheme, address, port, opts)
|
||||
|
||||
[:http2] ->
|
||||
HTTP2.connect(scheme, address, port, opts)
|
||||
|
||||
[:http1, :http2] ->
|
||||
transport_connect(scheme, address, port, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@spec upgrade(
|
||||
module(),
|
||||
Types.socket(),
|
||||
Types.scheme(),
|
||||
String.t(),
|
||||
:inet.port_number(),
|
||||
keyword()
|
||||
) :: {:ok, Mint.HTTP.t()} | {:error, Types.error()}
|
||||
def upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) do
|
||||
{protocols, opts} = Keyword.pop(opts, :protocols, @default_protocols)
|
||||
|
||||
case Enum.sort(protocols) do
|
||||
[:http1] ->
|
||||
HTTP1.upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts)
|
||||
|
||||
[:http2] ->
|
||||
HTTP2.upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts)
|
||||
|
||||
[:http1, :http2] ->
|
||||
transport_upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@spec initiate(module(), Types.socket(), String.t(), :inet.port_number(), keyword()) ::
|
||||
{:ok, Mint.HTTP.t()} | {:error, Types.error()}
|
||||
def initiate(transport, transport_state, hostname, port, opts),
|
||||
do: alpn_negotiate(transport, transport_state, hostname, port, opts)
|
||||
|
||||
defp transport_connect(:http, address, port, opts) do
|
||||
# HTTP1 upgrade is not supported
|
||||
HTTP1.connect(:http, address, port, opts)
|
||||
end
|
||||
|
||||
defp transport_connect(:https, address, port, opts) do
|
||||
connect_negotiate(:https, address, port, opts)
|
||||
end
|
||||
|
||||
defp connect_negotiate(scheme, address, port, opts) do
|
||||
transport = Util.scheme_to_transport(scheme)
|
||||
hostname = Mint.Core.Util.hostname(opts, address)
|
||||
|
||||
transport_opts =
|
||||
opts
|
||||
|> Keyword.get(:transport_opts, [])
|
||||
|> Keyword.merge(@transport_opts)
|
||||
|> Keyword.put(:hostname, hostname)
|
||||
|
||||
with {:ok, transport_state} <- transport.connect(address, port, transport_opts) do
|
||||
alpn_negotiate(scheme, transport_state, hostname, port, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp transport_upgrade(
|
||||
proxy_scheme,
|
||||
transport_state,
|
||||
:http,
|
||||
hostname,
|
||||
port,
|
||||
opts
|
||||
) do
|
||||
# HTTP1 upgrade is not supported
|
||||
HTTP1.upgrade(proxy_scheme, transport_state, :http, hostname, port, opts)
|
||||
end
|
||||
|
||||
defp transport_upgrade(
|
||||
proxy_scheme,
|
||||
transport_state,
|
||||
:https,
|
||||
hostname,
|
||||
port,
|
||||
opts
|
||||
) do
|
||||
connect_upgrade(proxy_scheme, transport_state, :https, hostname, port, opts)
|
||||
end
|
||||
|
||||
defp connect_upgrade(proxy_scheme, transport_state, new_scheme, hostname, port, opts) do
|
||||
transport = Util.scheme_to_transport(new_scheme)
|
||||
|
||||
transport_opts =
|
||||
opts
|
||||
|> Keyword.get(:transport_opts, [])
|
||||
|> Keyword.merge(@transport_opts)
|
||||
|
||||
case transport.upgrade(transport_state, proxy_scheme, hostname, port, transport_opts) do
|
||||
{:ok, transport_state} ->
|
||||
alpn_negotiate(new_scheme, transport_state, hostname, port, opts)
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, %TransportError{reason: reason}}
|
||||
end
|
||||
end
|
||||
|
||||
defp alpn_negotiate(scheme, socket, hostname, port, opts) do
|
||||
transport = Util.scheme_to_transport(scheme)
|
||||
|
||||
case transport.negotiated_protocol(socket) do
|
||||
{:ok, "http/1.1"} ->
|
||||
HTTP1.initiate(scheme, socket, hostname, port, opts)
|
||||
|
||||
{:ok, "h2"} ->
|
||||
HTTP2.initiate(scheme, socket, hostname, port, opts)
|
||||
|
||||
{:error, %TransportError{reason: :protocol_not_negotiated}} ->
|
||||
# Assume HTTP1 if ALPN is not supported
|
||||
HTTP1.initiate(scheme, socket, hostname, port, opts)
|
||||
|
||||
{:ok, protocol} ->
|
||||
{:error, %TransportError{reason: {:bad_alpn_protocol, protocol}}}
|
||||
|
||||
{:error, %TransportError{} = error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
92
phoenix/deps/mint/lib/mint/transport_error.ex
Normal file
92
phoenix/deps/mint/lib/mint/transport_error.ex
Normal file
@@ -0,0 +1,92 @@
|
||||
defmodule Mint.TransportError do
|
||||
@moduledoc """
|
||||
Represents an error with the transport used by an HTTP connection.
|
||||
|
||||
A `Mint.TransportError` struct is an exception, so it can be raised as any
|
||||
other exception.
|
||||
|
||||
## Struct fields
|
||||
|
||||
This exception represents an error with the transport (TCP or SSL) used
|
||||
by an HTTP connection. The exception struct itself is opaque, that is,
|
||||
not all fields are public. The following are the public fields:
|
||||
|
||||
* `:reason` - a term representing the error reason. The value of this field
|
||||
can be:
|
||||
|
||||
* `:timeout` - if there's a timeout in interacting with the socket.
|
||||
|
||||
* `:closed` - if the connection has been closed.
|
||||
|
||||
* `:protocol_not_negotiated` - if the ALPN protocol negotiation failed.
|
||||
|
||||
* `{:bad_alpn_protocol, protocol}` - when the ALPN protocol is not
|
||||
one of the supported protocols, which are `http/1.1` and `h2`.
|
||||
|
||||
* `t::inet.posix/0` - if there's any other error with the socket,
|
||||
such as `:econnrefused` or `:nxdomain`.
|
||||
|
||||
* `t::ssl.error_alert/0` - if there's an SSL error.
|
||||
|
||||
## Message representation
|
||||
|
||||
If you want to convert an error reason to a human-friendly message (for example
|
||||
for using in logs), you can use `Exception.message/1`:
|
||||
|
||||
iex> {:error, %Mint.TransportError{} = error} = Mint.HTTP.connect(:http, "nonexistent", 80)
|
||||
iex> Exception.message(error)
|
||||
"non-existing domain"
|
||||
|
||||
"""
|
||||
|
||||
reason_type =
|
||||
quote do
|
||||
:timeout
|
||||
| :closed
|
||||
| :protocol_not_negotiated
|
||||
| {:bad_alpn_protocol, String.t()}
|
||||
| :inet.posix()
|
||||
end
|
||||
|
||||
reason_type =
|
||||
if System.otp_release() >= "21" do
|
||||
quote do: unquote(reason_type) | :ssl.error_alert()
|
||||
else
|
||||
reason_type
|
||||
end
|
||||
|
||||
@type t() :: %__MODULE__{reason: unquote(reason_type) | term()}
|
||||
|
||||
defexception [:reason]
|
||||
|
||||
def message(%__MODULE__{reason: reason}) do
|
||||
format_reason(reason)
|
||||
end
|
||||
|
||||
## Our reasons.
|
||||
|
||||
defp format_reason(:protocol_not_negotiated) do
|
||||
"ALPN protocol not negotiated"
|
||||
end
|
||||
|
||||
defp format_reason({:bad_alpn_protocol, protocol}) do
|
||||
"bad ALPN protocol #{inspect(protocol)}, supported protocols are \"http/1.1\" and \"h2\""
|
||||
end
|
||||
|
||||
defp format_reason(:closed) do
|
||||
"socket closed"
|
||||
end
|
||||
|
||||
defp format_reason(:timeout) do
|
||||
"timeout"
|
||||
end
|
||||
|
||||
# :ssl.format_error/1 falls back to :inet.format_error/1 when the error is not an SSL-specific
|
||||
# error (at least since OTP 19+), so we can just use that.
|
||||
defp format_reason(reason) do
|
||||
case :ssl.format_error(reason) do
|
||||
~c"Unexpected error:" ++ _ -> inspect(reason)
|
||||
message -> List.to_string(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
148
phoenix/deps/mint/lib/mint/tunnel_proxy.ex
Normal file
148
phoenix/deps/mint/lib/mint/tunnel_proxy.ex
Normal file
@@ -0,0 +1,148 @@
|
||||
defmodule Mint.TunnelProxy do
|
||||
@moduledoc false
|
||||
|
||||
alias Mint.{HTTP, HTTP1, HTTPError, Negotiate, TransportError}
|
||||
|
||||
@tunnel_timeout 30_000
|
||||
|
||||
@spec connect(tuple(), tuple()) :: {:ok, Mint.HTTP.t()} | {:error, term()}
|
||||
def connect(proxy, host) do
|
||||
case establish_proxy(proxy, host) do
|
||||
{:ok, conn} -> upgrade_connection(conn, proxy, host)
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp establish_proxy(proxy, host) do
|
||||
{proxy_scheme, proxy_address, proxy_port, proxy_opts} = proxy
|
||||
{_scheme, address, port, opts} = host
|
||||
hostname = Mint.Core.Util.hostname(opts, address)
|
||||
|
||||
path = "#{hostname}:#{port}"
|
||||
|
||||
with {:ok, conn} <- HTTP1.connect(proxy_scheme, proxy_address, proxy_port, proxy_opts),
|
||||
timeout_deadline = timeout_deadline(proxy_opts),
|
||||
headers = Keyword.get(opts, :proxy_headers, []),
|
||||
{:ok, conn, ref} <- HTTP1.request(conn, "CONNECT", path, headers, nil),
|
||||
{:ok, proxy_headers} <- receive_response(conn, ref, timeout_deadline) do
|
||||
{:ok, HTTP1.put_proxy_headers(conn, proxy_headers)}
|
||||
else
|
||||
{:error, reason} ->
|
||||
{:error, wrap_in_proxy_error(reason)}
|
||||
|
||||
{:error, conn, reason} ->
|
||||
{:ok, _conn} = HTTP1.close(conn)
|
||||
{:error, wrap_in_proxy_error(reason)}
|
||||
end
|
||||
end
|
||||
|
||||
defp upgrade_connection(
|
||||
conn,
|
||||
{proxy_scheme, _proxy_address, _proxy_port, _proxy_opts} = _proxy,
|
||||
{scheme, hostname, port, opts} = _host
|
||||
) do
|
||||
proxy_headers = HTTP1.get_proxy_headers(conn)
|
||||
socket = HTTP1.get_socket(conn)
|
||||
|
||||
# Note that we may leak messages if the server sent data after the CONNECT response
|
||||
case Negotiate.upgrade(proxy_scheme, socket, scheme, hostname, port, opts) do
|
||||
{:ok, conn} -> {:ok, HTTP.put_proxy_headers(conn, proxy_headers)}
|
||||
{:error, reason} -> {:error, wrap_in_proxy_error(reason)}
|
||||
end
|
||||
end
|
||||
|
||||
defp receive_response(conn, ref, timeout_deadline) do
|
||||
timeout = timeout_deadline - System.monotonic_time(:millisecond)
|
||||
socket = HTTP1.get_socket(conn)
|
||||
|
||||
receive do
|
||||
{tag, ^socket, _data} = msg when tag in [:tcp, :ssl] ->
|
||||
stream(conn, ref, timeout_deadline, msg)
|
||||
|
||||
{tag, ^socket} = msg when tag in [:tcp_closed, :ssl_closed] ->
|
||||
stream(conn, ref, timeout_deadline, msg)
|
||||
|
||||
{tag, ^socket, _reason} = msg when tag in [:tcp_error, :ssl_error] ->
|
||||
stream(conn, ref, timeout_deadline, msg)
|
||||
after
|
||||
timeout ->
|
||||
{:error, conn, wrap_error({:proxy, :tunnel_timeout})}
|
||||
end
|
||||
end
|
||||
|
||||
defp stream(conn, ref, timeout_deadline, msg) do
|
||||
case HTTP1.stream(conn, msg) do
|
||||
{:ok, conn, responses} ->
|
||||
case handle_responses(ref, timeout_deadline, responses) do
|
||||
{:done, proxy_headers} -> {:ok, proxy_headers}
|
||||
:more -> receive_response(conn, ref, timeout_deadline)
|
||||
{:error, reason} -> {:error, conn, reason}
|
||||
end
|
||||
|
||||
{:error, conn, reason, _responses} ->
|
||||
{:error, conn, wrap_in_proxy_error(reason)}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_responses(ref, timeout_deadline, [response | responses]) do
|
||||
case response do
|
||||
{:status, ^ref, status} when status in 200..299 ->
|
||||
handle_responses(ref, timeout_deadline, responses)
|
||||
|
||||
{:status, ^ref, status} ->
|
||||
{:error, wrap_error({:proxy, {:unexpected_status, status}})}
|
||||
|
||||
{:headers, ^ref, headers} when responses == [] ->
|
||||
{:done, headers}
|
||||
|
||||
{:headers, ^ref, _headers} ->
|
||||
{:error, wrap_error({:proxy, {:unexpected_trailing_responses, responses}})}
|
||||
|
||||
{:error, ^ref, reason} ->
|
||||
{:error, wrap_in_proxy_error(reason)}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_responses(_ref, _timeout_deadline, []) do
|
||||
:more
|
||||
end
|
||||
|
||||
defp timeout_deadline(opts) do
|
||||
timeout = Keyword.get(opts, :tunnel_timeout, @tunnel_timeout)
|
||||
System.monotonic_time(:millisecond) + timeout
|
||||
end
|
||||
|
||||
defp wrap_error(reason) do
|
||||
%HTTPError{module: __MODULE__, reason: reason}
|
||||
end
|
||||
|
||||
defp wrap_in_proxy_error(%HTTPError{reason: {:proxy, _}} = error) do
|
||||
error
|
||||
end
|
||||
|
||||
defp wrap_in_proxy_error(%HTTPError{reason: reason}) do
|
||||
%HTTPError{module: __MODULE__, reason: {:proxy, reason}}
|
||||
end
|
||||
|
||||
defp wrap_in_proxy_error(%TransportError{} = error) do
|
||||
error
|
||||
end
|
||||
|
||||
@doc false
|
||||
def format_error({:proxy, reason}) do
|
||||
case reason do
|
||||
:tunnel_timeout ->
|
||||
"proxy tunnel timeout"
|
||||
|
||||
{:unexpected_status, status} ->
|
||||
"expected tunnel proxy to return a status between 200 and 299, got: #{inspect(status)}"
|
||||
|
||||
{:unexpected_trailing_responses, responses} ->
|
||||
"tunnel proxy returned unexpected trailer responses: #{inspect(responses)}"
|
||||
|
||||
http_reason ->
|
||||
"error when establishing the tunnel proxy connection: " <>
|
||||
HTTP1.format_error(http_reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
74
phoenix/deps/mint/lib/mint/types.ex
Normal file
74
phoenix/deps/mint/lib/mint/types.ex
Normal file
@@ -0,0 +1,74 @@
|
||||
defmodule Mint.Types do
|
||||
@moduledoc """
|
||||
HTTP-related types.
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
A hostname, IP address, Unix domain socket path, `:loopback`, or any
|
||||
other term representing an internet address.
|
||||
"""
|
||||
@type address() :: :inet.socket_address() | String.t()
|
||||
|
||||
@typedoc """
|
||||
A request reference that uniquely identifies a request.
|
||||
|
||||
Responses for a request are always tagged with a request reference so that you
|
||||
can connect each response to the right request. Also see `Mint.HTTP.request/5`.
|
||||
"""
|
||||
@type request_ref() :: reference()
|
||||
|
||||
@typedoc """
|
||||
An HTTP/2-specific response to a request.
|
||||
|
||||
This type of response is only returned on HTTP/2 connections. See `t:response/0` for
|
||||
more response types.
|
||||
"""
|
||||
@type http2_response() ::
|
||||
{:pong, request_ref()}
|
||||
| {:push_promise, request_ref(), promised_request_ref :: request_ref(), headers()}
|
||||
|
||||
@typedoc """
|
||||
A response to a request.
|
||||
|
||||
Terms of this type are returned as responses to requests. See `Mint.HTTP.stream/2`
|
||||
for more information.
|
||||
"""
|
||||
@type response() ::
|
||||
{:status, request_ref(), status()}
|
||||
| {:headers, request_ref(), headers()}
|
||||
| {:data, request_ref(), body_chunk :: binary()}
|
||||
| {:done, request_ref()}
|
||||
| {:error, request_ref(), reason :: term()}
|
||||
| http2_response()
|
||||
|
||||
@typedoc """
|
||||
An HTTP status code.
|
||||
|
||||
The type for an HTTP is a generic non-negative integer since we don't formally check that
|
||||
the response code is in the "common" range (`200..599`).
|
||||
"""
|
||||
@type status() :: non_neg_integer()
|
||||
|
||||
@typedoc """
|
||||
HTTP headers.
|
||||
|
||||
Headers are sent and received as lists of two-element tuples containing two strings,
|
||||
the header name and header value.
|
||||
"""
|
||||
@type headers() :: [{header_name :: String.t(), header_value :: String.t()}]
|
||||
|
||||
@typedoc """
|
||||
The scheme to use when connecting to an HTTP server.
|
||||
"""
|
||||
@type scheme() :: :http | :https
|
||||
|
||||
@typedoc """
|
||||
An error reason.
|
||||
"""
|
||||
@type error() :: Mint.TransportError.t() | Mint.HTTPError.t()
|
||||
|
||||
@typedoc """
|
||||
The connection socket.
|
||||
"""
|
||||
@type socket() :: term()
|
||||
end
|
||||
202
phoenix/deps/mint/lib/mint/unsafe_proxy.ex
Normal file
202
phoenix/deps/mint/lib/mint/unsafe_proxy.ex
Normal file
@@ -0,0 +1,202 @@
|
||||
defmodule Mint.UnsafeProxy do
|
||||
@moduledoc false
|
||||
|
||||
alias Mint.{Types, UnsafeProxy}
|
||||
|
||||
@behaviour Mint.Core.Conn
|
||||
|
||||
defstruct [
|
||||
:hostname,
|
||||
:port,
|
||||
:scheme,
|
||||
:module,
|
||||
:proxy_headers,
|
||||
:state
|
||||
]
|
||||
|
||||
@opaque t() :: %UnsafeProxy{}
|
||||
|
||||
@type host_triple() :: {Types.scheme(), address :: Types.address(), :inet.port_number()}
|
||||
|
||||
@spec connect(host_triple(), host_triple(), opts :: keyword()) ::
|
||||
{:ok, t()} | {:error, Types.error()}
|
||||
def connect(proxy, host, opts \\ []) do
|
||||
{proxy_scheme, proxy_address, proxy_port} = proxy
|
||||
{scheme, address, port} = host
|
||||
hostname = Mint.Core.Util.hostname(opts, address)
|
||||
|
||||
with {:ok, state} <- Mint.HTTP1.connect(proxy_scheme, proxy_address, proxy_port, opts) do
|
||||
conn = %UnsafeProxy{
|
||||
scheme: scheme,
|
||||
hostname: hostname,
|
||||
port: port,
|
||||
module: Mint.HTTP1,
|
||||
proxy_headers: Keyword.get(opts, :proxy_headers, []),
|
||||
state: state
|
||||
}
|
||||
|
||||
{:ok, conn}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec initiate(
|
||||
module(),
|
||||
Mint.Types.socket(),
|
||||
String.t(),
|
||||
:inet.port_number(),
|
||||
keyword()
|
||||
) :: no_return()
|
||||
def initiate(_transport, _transport_state, _hostname, _port, _opts) do
|
||||
raise "initiate/5 does not apply for #{inspect(__MODULE__)}"
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec close(t()) :: {:ok, t()}
|
||||
def close(%UnsafeProxy{module: module, state: state} = _conn) do
|
||||
module.close(state)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec open?(t(), :read | :write) :: boolean()
|
||||
def open?(%UnsafeProxy{module: module, state: state}, type \\ :write) do
|
||||
module.open?(state, type)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec request(
|
||||
t(),
|
||||
method :: String.t(),
|
||||
path :: String.t(),
|
||||
Types.headers(),
|
||||
body :: iodata() | nil | :stream
|
||||
) ::
|
||||
{:ok, t(), Types.request_ref()}
|
||||
| {:error, t(), Types.error()}
|
||||
def request(
|
||||
%UnsafeProxy{module: module, state: state} = conn,
|
||||
method,
|
||||
path,
|
||||
headers,
|
||||
body \\ nil
|
||||
) do
|
||||
path = request_line(conn, path)
|
||||
headers = headers ++ conn.proxy_headers
|
||||
|
||||
case module.request(state, method, path, headers, body) do
|
||||
{:ok, state, request} -> {:ok, %{conn | state: state}, request}
|
||||
{:error, state, reason} -> {:error, %{conn | state: state}, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec stream_request_body(
|
||||
t(),
|
||||
Types.request_ref(),
|
||||
iodata() | :eof | {:eof, trailer_headers :: Types.headers()}
|
||||
) ::
|
||||
{:ok, t()} | {:error, t(), Types.error()}
|
||||
def stream_request_body(%UnsafeProxy{module: module, state: state} = conn, ref, body) do
|
||||
case module.stream_request_body(state, ref, body) do
|
||||
{:ok, state} -> {:ok, %{conn | state: state}}
|
||||
{:error, state, reason} -> {:error, %{conn | state: state}, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec stream(t(), term()) ::
|
||||
{:ok, t(), [Types.response()]}
|
||||
| {:error, t(), Types.error(), [Types.response()]}
|
||||
| :unknown
|
||||
def stream(%UnsafeProxy{module: module, state: state} = conn, message) do
|
||||
case module.stream(state, message) do
|
||||
{:ok, state, responses} -> {:ok, %{conn | state: state}, responses}
|
||||
{:error, state, reason, responses} -> {:error, %{conn | state: state}, reason, responses}
|
||||
:unknown -> :unknown
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec open_request_count(t()) :: non_neg_integer()
|
||||
def open_request_count(%UnsafeProxy{module: module, state: state} = _conn) do
|
||||
module.open_request_count(state)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec recv(t(), non_neg_integer(), timeout()) ::
|
||||
{:ok, t(), [Types.response()]}
|
||||
| {:error, t(), Types.error(), [Types.response()]}
|
||||
def recv(%UnsafeProxy{module: module, state: state} = conn, byte_count, timeout) do
|
||||
case module.recv(state, byte_count, timeout) do
|
||||
{:ok, state, responses} -> {:ok, %{conn | state: state}, responses}
|
||||
{:error, state, reason, responses} -> {:error, %{conn | state: state}, reason, responses}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec set_mode(t(), :active | :passive) :: {:ok, t()} | {:error, Types.error()}
|
||||
def set_mode(%UnsafeProxy{module: module, state: state} = conn, mode) do
|
||||
with {:ok, state} <- module.set_mode(state, mode) do
|
||||
{:ok, %{conn | state: state}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec controlling_process(t(), pid()) :: {:ok, t()} | {:error, Types.error()}
|
||||
def controlling_process(%UnsafeProxy{module: module, state: state} = conn, new_pid) do
|
||||
with {:ok, _} <- module.controlling_process(state, new_pid) do
|
||||
{:ok, conn}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec put_private(t(), atom(), term()) :: t()
|
||||
def put_private(%UnsafeProxy{module: module, state: state} = conn, key, value) do
|
||||
state = module.put_private(state, key, value)
|
||||
%{conn | state: state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec get_private(t(), atom(), term()) :: term()
|
||||
def get_private(%UnsafeProxy{module: module, state: state}, key, default \\ nil) do
|
||||
module.get_private(state, key, default)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec delete_private(t(), atom()) :: t()
|
||||
def delete_private(%UnsafeProxy{module: module, state: state} = conn, key) do
|
||||
state = module.delete_private(state, key)
|
||||
%{conn | state: state}
|
||||
end
|
||||
|
||||
defp request_line(%UnsafeProxy{scheme: scheme, hostname: hostname, port: port}, path) do
|
||||
%URI{scheme: Atom.to_string(scheme), host: hostname, port: port, path: path}
|
||||
|> URI.to_string()
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec get_socket(t()) :: Mint.Types.socket()
|
||||
def get_socket(%UnsafeProxy{module: module, state: state}) do
|
||||
module.get_socket(state)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec put_log(t(), boolean()) :: t()
|
||||
def put_log(%UnsafeProxy{module: module, state: state} = conn, log) do
|
||||
state = module.put_log(state, log)
|
||||
%{conn | state: state}
|
||||
end
|
||||
|
||||
# The `%__MODULE__{proxy_headers: value}` here is the request headers,
|
||||
# not the proxy response ones. Unsafe proxy mixes its headers (if any)
|
||||
# with the regular response headers, so you can get them there.
|
||||
@impl true
|
||||
@spec get_proxy_headers(t()) :: Mint.Types.headers()
|
||||
def get_proxy_headers(%__MODULE__{}), do: []
|
||||
|
||||
@impl true
|
||||
@spec put_proxy_headers(t(), Mint.Types.headers()) :: t()
|
||||
def put_proxy_headers(%__MODULE__{}, _headers) do
|
||||
raise "invalid function for proxy unsafe proxy connections"
|
||||
end
|
||||
end
|
||||
86
phoenix/deps/mint/mix.exs
Normal file
86
phoenix/deps/mint/mix.exs
Normal file
@@ -0,0 +1,86 @@
|
||||
defmodule Mint.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "1.7.1"
|
||||
@repo_url "https://github.com/elixir-mint/mint"
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :mint,
|
||||
version: @version,
|
||||
elixir: "~> 1.12",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
deps: deps(),
|
||||
|
||||
# Xref
|
||||
xref: [
|
||||
exclude: [
|
||||
:persistent_term,
|
||||
{:ssl, :cipher_suites, 1},
|
||||
{:public_key, :cacerts_get, 0},
|
||||
CAStore
|
||||
]
|
||||
],
|
||||
|
||||
# Dialyxir
|
||||
dialyzer: [
|
||||
plt_add_apps: [:castore],
|
||||
plt_local_path: "plts",
|
||||
plt_core_path: "plts"
|
||||
],
|
||||
|
||||
# Code coverage
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
preferred_cli_env: ["coveralls.html": :test, coveralls: :test],
|
||||
|
||||
# Hex
|
||||
package: package(),
|
||||
description: "Small and composable HTTP client.",
|
||||
|
||||
# Docs
|
||||
name: "Mint",
|
||||
docs: [
|
||||
source_ref: "v#{@version}",
|
||||
source_url: @repo_url,
|
||||
extras: [
|
||||
"pages/Architecture.md",
|
||||
"pages/Decompression.md"
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger, :ssl],
|
||||
mod: {Mint.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
defp package do
|
||||
[
|
||||
licenses: ["Apache-2.0"],
|
||||
links: %{"GitHub" => @repo_url}
|
||||
]
|
||||
end
|
||||
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_env), do: ["lib"]
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:castore, "~> 0.1.0 or ~> 1.0", optional: true},
|
||||
{:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0"},
|
||||
|
||||
# Dev/test dependencies
|
||||
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
|
||||
{:ex_doc, "~> 0.20", only: :dev},
|
||||
{:excoveralls, "~> 0.18.0", only: :test},
|
||||
{:mox, "~> 1.0", only: :test},
|
||||
{:stream_data, "~> 1.0", only: [:dev, :test]}
|
||||
]
|
||||
end
|
||||
end
|
||||
236
phoenix/deps/mint/src/mint_shims.erl
Normal file
236
phoenix/deps/mint/src/mint_shims.erl
Normal file
@@ -0,0 +1,236 @@
|
||||
%% Shims for functions introduced in recent Erlang/OTP releases,
|
||||
%% to enable use of Mint on older releases. The code in this module
|
||||
%% was taken directly from the Erlang/OTP project.
|
||||
%%
|
||||
%% File: lib/public_key/src/public_key.erl
|
||||
%% Tag: OTP-20.3.4
|
||||
%% Commit: f2c1d537dc28ffbde5d42aedec70bf4c6574c3ea
|
||||
%% Changes from original file:
|
||||
%% - extracted pkix_verify_hostname/2 and /3, and any private
|
||||
%% functions they depend upon
|
||||
%% - replaced local calls to other public functions in the
|
||||
%% 'public_key' module with fully qualified equivalents
|
||||
%% - replaced local type references with fully qualified equivalents
|
||||
%%
|
||||
%% The original license follows:
|
||||
|
||||
%% %CopyrightBegin%
|
||||
%%
|
||||
%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%
|
||||
%% %CopyrightEnd%
|
||||
%%
|
||||
-module(mint_shims).
|
||||
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
|
||||
-export([pkix_verify_hostname/2, pkix_verify_hostname/3]).
|
||||
|
||||
%--------------------------------------------------------------------
|
||||
-spec pkix_verify_hostname(Cert :: #'OTPCertificate'{} | binary(),
|
||||
ReferenceIDs :: [{uri_id | dns_id | ip | srv_id | public_key:oid(), string()}]) -> boolean().
|
||||
|
||||
-spec pkix_verify_hostname(Cert :: #'OTPCertificate'{} | binary(),
|
||||
ReferenceIDs :: [{uri_id | dns_id | ip | srv_id | public_key:oid(), string()}],
|
||||
Options :: proplists:proplist()) -> boolean().
|
||||
|
||||
%% Description: Validates a hostname to RFC 6125
|
||||
%%--------------------------------------------------------------------
|
||||
pkix_verify_hostname(Cert, ReferenceIDs) ->
|
||||
pkix_verify_hostname(Cert, ReferenceIDs, []).
|
||||
|
||||
pkix_verify_hostname(BinCert, ReferenceIDs, Options) when is_binary(BinCert) ->
|
||||
pkix_verify_hostname(public_key:pkix_decode_cert(BinCert,otp), ReferenceIDs, Options);
|
||||
|
||||
pkix_verify_hostname(Cert = #'OTPCertificate'{tbsCertificate = TbsCert}, ReferenceIDs0, Opts) ->
|
||||
MatchFun = proplists:get_value(match_fun, Opts, undefined),
|
||||
FailCB = proplists:get_value(fail_callback, Opts, fun(_Cert) -> false end),
|
||||
FqdnFun = proplists:get_value(fqdn_fun, Opts, fun verify_hostname_extract_fqdn_default/1),
|
||||
|
||||
ReferenceIDs = [{T,to_string(V)} || {T,V} <- ReferenceIDs0],
|
||||
PresentedIDs =
|
||||
try lists:keyfind(?'id-ce-subjectAltName',
|
||||
#'Extension'.extnID,
|
||||
TbsCert#'OTPTBSCertificate'.extensions)
|
||||
of
|
||||
#'Extension'{extnValue = ExtVals} ->
|
||||
[{T,to_string(V)} || {T,V} <- ExtVals];
|
||||
false ->
|
||||
[]
|
||||
catch
|
||||
_:_ -> []
|
||||
end,
|
||||
%% PresentedIDs example: [{dNSName,"ewstest.ericsson.com"}, {dNSName,"www.ericsson.com"}]}
|
||||
case PresentedIDs of
|
||||
[] ->
|
||||
%% Fallback to CN-ids [rfc6125, ch6]
|
||||
case TbsCert#'OTPTBSCertificate'.subject of
|
||||
{rdnSequence,RDNseq} ->
|
||||
PresentedCNs =
|
||||
[{cn, to_string(V)}
|
||||
|| ATVs <- RDNseq, % RDNseq is list-of-lists
|
||||
#'AttributeTypeAndValue'{type = ?'id-at-commonName',
|
||||
value = {_T,V}} <- ATVs
|
||||
% _T = kind of string (teletexString etc)
|
||||
],
|
||||
%% Example of PresentedCNs: [{cn,"www.ericsson.se"}]
|
||||
%% match ReferenceIDs to PresentedCNs
|
||||
verify_hostname_match_loop(verify_hostname_fqnds(ReferenceIDs, FqdnFun),
|
||||
PresentedCNs,
|
||||
MatchFun, FailCB, Cert);
|
||||
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
%% match ReferenceIDs to PresentedIDs
|
||||
case verify_hostname_match_loop(ReferenceIDs, PresentedIDs,
|
||||
MatchFun, FailCB, Cert) of
|
||||
false ->
|
||||
%% Try to extract DNS-IDs from URIs etc
|
||||
DNS_ReferenceIDs =
|
||||
[{dns_id,X} || X <- verify_hostname_fqnds(ReferenceIDs, FqdnFun)],
|
||||
verify_hostname_match_loop(DNS_ReferenceIDs, PresentedIDs,
|
||||
MatchFun, FailCB, Cert);
|
||||
true ->
|
||||
true
|
||||
end
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------
|
||||
%%% pkix_verify_hostname help functions
|
||||
verify_hostname_extract_fqdn_default({dns_id,S}) ->
|
||||
S;
|
||||
verify_hostname_extract_fqdn_default({uri_id,URI}) ->
|
||||
% Modified from original to remove dependency on http_uri:parse/1 from inets
|
||||
#{scheme := <<"https">>, host := Host} = 'Elixir.URI':parse(list_to_binary(URI)),
|
||||
binary_to_list(Host).
|
||||
|
||||
|
||||
verify_hostname_fqnds(L, FqdnFun) ->
|
||||
[E || E0 <- L,
|
||||
E <- [try case FqdnFun(E0) of
|
||||
default -> verify_hostname_extract_fqdn_default(E0);
|
||||
undefined -> undefined; % will make the "is_list(E)" test fail
|
||||
Other -> Other
|
||||
end
|
||||
catch _:_-> undefined % will make the "is_list(E)" test fail
|
||||
end],
|
||||
is_list(E),
|
||||
E =/= "",
|
||||
{error,einval} == inet:parse_address(E)
|
||||
].
|
||||
|
||||
|
||||
-define(srvName_OID, {1,3,6,1,4,1,434,2,2,1,37,0}).
|
||||
|
||||
verify_hostname_match_default(Ref, Pres) ->
|
||||
verify_hostname_match_default0(to_lower_ascii(Ref), to_lower_ascii(Pres)).
|
||||
|
||||
verify_hostname_match_default0(FQDN=[_|_], {cn,FQDN}) ->
|
||||
not lists:member($*, FQDN);
|
||||
verify_hostname_match_default0(FQDN=[_|_], {cn,Name=[_|_]}) ->
|
||||
[F1|Fs] = string:tokens(FQDN, "."),
|
||||
[N1|Ns] = string:tokens(Name, "."),
|
||||
match_wild(F1,N1) andalso Fs==Ns;
|
||||
verify_hostname_match_default0({dns_id,R}, {dNSName,P}) ->
|
||||
R==P;
|
||||
verify_hostname_match_default0({uri_id,R}, {uniformResourceIdentifier,P}) ->
|
||||
R==P;
|
||||
verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 4 ->
|
||||
%% IPv4
|
||||
try
|
||||
list_to_tuple(P)
|
||||
== if is_tuple(R), size(R)==4 -> R;
|
||||
is_list(R) -> ok(inet:parse_ipv4strict_address(R))
|
||||
end
|
||||
catch
|
||||
_:_ ->
|
||||
false
|
||||
end;
|
||||
|
||||
verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 16 ->
|
||||
%% IPv6. The length 16 is due to the certificate specification.
|
||||
try
|
||||
l16_to_tup(P)
|
||||
== if is_tuple(R), size(R)==8 -> R;
|
||||
is_list(R) -> ok(inet:parse_ipv6strict_address(R))
|
||||
end
|
||||
catch
|
||||
_:_ ->
|
||||
false
|
||||
end;
|
||||
verify_hostname_match_default0({srv_id,R}, {srvName,P}) ->
|
||||
R==P;
|
||||
verify_hostname_match_default0({srv_id,R}, {?srvName_OID,P}) ->
|
||||
R==P;
|
||||
verify_hostname_match_default0(_, _) ->
|
||||
false.
|
||||
|
||||
ok({ok,X}) -> X.
|
||||
|
||||
l16_to_tup(L) -> list_to_tuple(l16_to_tup(L, [])).
|
||||
%%
|
||||
l16_to_tup([A,B|T], Acc) -> l16_to_tup(T, [(A bsl 8) bor B | Acc]);
|
||||
l16_to_tup([], Acc) -> lists:reverse(Acc).
|
||||
|
||||
match_wild(A, [$*|B]) -> match_wild_suffixes(A, B);
|
||||
match_wild([C|A], [ C|B]) -> match_wild(A, B);
|
||||
match_wild([], []) -> true;
|
||||
match_wild(_, _) -> false.
|
||||
|
||||
%% Match the parts after the only wildcard by comparing them from the end
|
||||
match_wild_suffixes(A, B) -> match_wild_sfx(lists:reverse(A), lists:reverse(B)).
|
||||
|
||||
match_wild_sfx([$*|_], _) -> false; % Bad name (no wildcards allowed)
|
||||
match_wild_sfx(_, [$*|_]) -> false; % Bad pattern (no more wildcards allowed)
|
||||
match_wild_sfx([A|Ar], [A|Br]) -> match_wild_sfx(Ar, Br);
|
||||
match_wild_sfx(Ar, []) -> not lists:member($*, Ar); % Chk for bad name (= wildcards)
|
||||
match_wild_sfx(_, _) -> false.
|
||||
|
||||
|
||||
verify_hostname_match_loop(Refs0, Pres0, undefined, FailCB, Cert) ->
|
||||
Pres = lists:map(fun to_lower_ascii/1, Pres0),
|
||||
Refs = lists:map(fun to_lower_ascii/1, Refs0),
|
||||
lists:any(
|
||||
fun(R) ->
|
||||
lists:any(fun(P) ->
|
||||
verify_hostname_match_default(R,P) orelse FailCB(Cert)
|
||||
end, Pres)
|
||||
end, Refs);
|
||||
verify_hostname_match_loop(Refs, Pres, MatchFun, FailCB, Cert) ->
|
||||
lists:any(
|
||||
fun(R) ->
|
||||
lists:any(fun(P) ->
|
||||
(case MatchFun(R,P) of
|
||||
default -> verify_hostname_match_default(R,P);
|
||||
Bool -> Bool
|
||||
end) orelse FailCB(Cert)
|
||||
end,
|
||||
Pres)
|
||||
end,
|
||||
Refs).
|
||||
|
||||
|
||||
to_lower_ascii({ip,_}=X) -> X;
|
||||
to_lower_ascii({iPAddress,_}=X) -> X;
|
||||
to_lower_ascii(S) when is_list(S) -> lists:map(fun to_lower_ascii/1, S);
|
||||
to_lower_ascii({T,S}) -> {T, to_lower_ascii(S)};
|
||||
to_lower_ascii(C) when $A =< C,C =< $Z -> C + ($a-$A);
|
||||
to_lower_ascii(C) -> C.
|
||||
|
||||
to_string(S) when is_list(S) -> S;
|
||||
to_string(B) when is_binary(B) -> binary_to_list(B);
|
||||
to_string(X) -> X.
|
||||
Reference in New Issue
Block a user