# `PhoenixKit.Modules.Storage.RemoteFetch`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.55.1/lib/modules/storage/services/remote_fetch.ex#L1)

Downloads a user-supplied URL to a temporary file without letting it reach
the server's own network.

A URL is a request for the server to connect somewhere, and the
"somewhere" is the attacker's to choose: `http://169.254.169.254/` (cloud
credentials), `http://localhost:5432/`, a name that resolves to an internal
address, or one that resolves to a public address when checked and an
internal one a moment later (DNS rebinding). So:

  * only `https` by default (`allow_http: true` to allow plain http), no
    credentials in the URL, ports 80/443 unless `allowed_ports:` says so;
  * the host is resolved once, **every** address it resolves to must be
    public (`PhoenixKit.Utils.PublicAddress`), and the connection is made
    to the address that was checked — with the original name for `Host`,
    SNI and certificate verification (Mint's `:hostname`), so nothing
    resolves the name again between the check and the connect;
  * redirects are followed by hand (at most `max_redirects`, default 3),
    each hop checked again, and an https URL never redirects to http;
  * the body is streamed to a temporary file and cut off at `max_bytes`
    (default 25 MB) whatever `Content-Length` claims, within one overall
    `timeout` (default 15 s).

`download/2` returns `{:ok, %{path: temp_path, filename: name, url: final_url}}`;
the caller removes the temporary file. What the bytes ARE is decided later,
by sniffing them (`Storage.store_from_url/2`), never from the response's
`Content-Type`.

# `reason`

```elixir
@type reason() ::
  :invalid_url
  | :scheme_not_allowed
  | :port_not_allowed
  | :blocked_host
  | :nxdomain
  | :too_many_redirects
  | :too_large
  | :timeout
  | {:http_status, pos_integer()}
  | {:connect_failed, term()}
  | {:write_failed, term()}
```

# `download`

```elixir
@spec download(String.t(), keyword()) ::
  {:ok, %{path: String.t(), filename: String.t(), url: String.t()}}
  | {:error, reason()}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
