Example script
Depending on your use case you can use this code in many ways:
Mix.install(~w[httpoison http_stream]a)
Application.put_env(:http_stream, :adapter, HTTPStream.Adapter.HTTPoison)
defmodule Example do
@url "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10-million-password-list-top-1000000.txt"
def sample do
password = "123456"
path = url_to_path(@url)
IO.inspect(Example.find(@url, password), label: "Example.find(url, password)")
IO.inspect(File.exists?(path), label: "File.exists?/1")
IO.inspect(Example.save(@url, path), label: "Example.save/3")
IO.inspect(File.exists?(path), label: "File.exists?/1")
IO.inspect(Example.find(path, password), label: "Example.find(path, password)")
File.rm(path)
IO.puts("Removed file before next save")
IO.inspect(Example.save_and_find(@url, path, password), label: "Example.save_and_find?/3")
IO.inspect(File.exists?(path), label: "File.exists?/1")
IO.puts("Cleanup (removed saved file)")
File.rm(path)
end
def save_and_find(url, path \\ nil, text, opts \\ []) do
path = ensure_path(path, url)
url |> ensure_saved(path, opts) |> find(text)
end
def find(path_or_url, text, opts \\ []) do
opts = Keyword.put(opts, :flatten, true)
path_or_url |> stream(opts) |> Enum.find(&(&1 == text))
end
defp ensure_saved(url, path, opts) do
force_save = opts[:force] || false
if force_save or not File.exists?(path), do: save(url, path, opts), else: path
end
def save(url, path \\ nil, opts \\ []) do
path = ensure_path(path, url)
File.touch(path)
file_stream = stream(path, Keyword.put(opts, :raw_stream, true))
url |> stream(opts) |> Stream.into(file_stream) |> Stream.run()
path
end
defp ensure_path(nil, url), do: url_to_path(url)
defp ensure_path(path, _url), do: path
def url_to_path(url), do: url |> URI.parse() |> Map.get(:path) |> Path.basename()
def stream(path_or_url, opts \\ []) do
host = URI.parse(path_or_url).host
if is_nil(host) do
raw_stream = opts[:raw_stream] || false
stream = File.stream!(path_or_url)
if raw_stream, do: stream, else: Stream.map(stream, &String.trim_trailing(&1, "\n"))
else
flatten = opts[:flatten] || false
stream = HTTPStream.get(path_or_url)
if flatten, do: Stream.flat_map(stream, &String.split(&1, "\n")), else: stream
end
end
end
Example.sample()
Notes
In some cases you may want to write downloaded file into memory instead of file. In that case you can easily modify above code to use for exampleets
table instead of File.stream!/1
as mentioned already.
As mentioned already you can use said stream
and transform it into list
and generate a functions at compile time with pattern matching, for example:
defmodule Password do
stream = Example.stream(url, flatten: true)
for password <- stream do
def valid?(unquote(password)), do: true
end
def valid?(_invalid_password), do: false
end
Helpful resources
Articles:
- Download Large Files with HTTPoison Async Requests
- Elixir Streams to process large HTTP responses on the fly
Dependencies:
- httpoison
- http_stream
Documentation:
- Application.put_env/3
- Enum.find/2
- File.exists?/1
- File.rm/1
- File.stream!/1
- File.touch/1
- HTTPStream.get/1
- IO.inspect/2
- IO.puts/1
- Keyword.put/3
- Map.get/2
- Mix.install/1
- Stream.flat_map/2
- Stream.into/2
- Stream.map/2
- String.trim_trailing/2
- URI.parse/1