[
  {
    "path": ".formatter.exs",
    "content": "[\n  inputs: [\"{mix,.formatter}.exs\", \"lib/**/*.ex\", \"integration/tests.exs\"],\n  line_length: 80,\n  subdirectories: [\"test\", \"integration/tests/*\"]\n]\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\nenv:\n  MIX_ENV: ci\n\n# The approach to the CI matrix:\n#\n# * In general, remote_ip only maintains support for the minimum supported\n#   version of Elixir, so we only need to test the versions listed in\n#   https://hexdocs.pm/elixir/compatibility-and-deprecations.html\n#\n# * To avoid combinatorial explosion of Elixir/OTP pairs, and to err on the\n#   side of caution, each Elixir version is paired with its lowest supported\n#   OTP version.\n#\n# * Additionally, the highest Elixir version is also paired with its highest\n#   supported OTP version, to cover the latest & greatest case.\n\njobs:\n  ci:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - elixir: '1.12'\n            otp: '23'\n          - elixir: '1.13'\n            otp: '23'\n          - elixir: '1.14'\n            otp: '23'\n          - elixir: '1.15'\n            otp: '24'\n          - elixir: '1.16'\n            otp: '24'\n          - elixir: '1.16'\n            otp: '26'\n\n    name: Elixir ${{ matrix.elixir }} (OTP ${{ matrix.otp }})\n\n    runs-on: ubuntu-20.04\n\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - id: install\n        name: Install Elixir\n        uses: erlef/setup-beam@v1\n        with:\n          otp-version: ${{ matrix.otp }}\n          elixir-version: ${{ matrix.elixir }}\n\n      - name: Restore cached build\n        uses: actions/cache@v3\n        with:\n          key: builds@elixir-${{ steps.install.outputs.elixir-version }}-otp-${{ steps.install.outputs.otp-version }}-mix-${{ hashFiles('mix.lock') }}\n          path: |\n            deps\n            _build\n\n      - name: Install dependencies\n        run: mix do deps.get, deps.compile\n\n      - name: Check formatting\n        run: mix format --check-formatted\n\n      - name: Compile\n        run: mix compile --warnings-as-errors\n\n      - name: Run unit tests\n        run: mix coveralls.github\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Restore cached integrations\n        uses: actions/cache@v3\n        with:\n          key: integrations@elixir-${{ steps.install.outputs.elixir-version }}-otp-${{ steps.install.outputs.otp-version }}-mix-${{ hashFiles('integration/tests/*/mix.lock') }}\n          path: |\n            integration/tests/*/deps\n            integration/tests/*/_build\n\n      - name: Run integration tests\n        run: mix integrate\n\n      - name: Restore cached PLTs\n        uses: actions/cache@v3\n        with:\n          key: plts@elixir-${{ steps.install.outputs.elixir-version }}-otp-${{ steps.install.outputs.otp-version }}-mix-${{ hashFiles('mix.lock') }}\n          path: |\n            priv/plts\n          restore-keys: |\n            plts@elixir-${{ steps.install.outputs.elixir-version }}-otp-${{ steps.install.outputs.otp-version }}-mix-\n\n      - name: Run dialyzer\n        run: mix dialyzer\n"
  },
  {
    "path": ".gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover\n\n# The directory Mix downloads your dependencies sources to.\n/deps\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Dialyzer's Persistent Lookup Tables (PLTs) cache expensive analyses, but they\n# change depending on the version of Elixir/OTP, so we don't want to commit the\n# outputs directly to version control. E.g., having one version's PLTs could\n# mess up continuous integration on a different version.\n/priv/plts/*.plt\n/priv/plts/*.plt.hash\n"
  },
  {
    "path": ".tool-versions",
    "content": "elixir 1.16-otp-26\nerlang 26.2.5\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n**Table Of Contents**\n* [Issues](#issues)\n  * [Getting the wrong IP](#getting-the-wrong-ip)\n  * [Other issues](#other-issues)\n* [Pull Requests](#pull-requests)\n  * [General guidelines](#general-guidelines)\n\n## Issues\n\n### Getting the wrong IP\n\nThere are many reasons you might not be getting the `remote_ip` value you expect. Before opening an issue, enable `RemoteIp.Debugger` and reproduce your problematic request.\n\n```elixir\nconfig :remote_ip, debug: true\n```\n\n```console\n$ mix deps.compile --force remote_ip\n```\n\nThen you should see logs like these:\n\n```\n[debug] Processing remote IP\n  headers: [\"x-forwarded-for\"]\n  parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n  proxies: [\"1.2.0.0/16\", \"2.3.4.5/32\"]\n  clients: [\"1.2.3.4/32\"]\n[debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n[debug] Parsing IPs from forwarding headers: [{\"x-forwarded-for\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n[debug] Parsed IPs from forwarding headers: [{1, 2, 3, 4}, {10, 0, 0, 1}, {2, 3, 4, 5}]\n[debug] {2, 3, 4, 5} is a known proxy IP\n[debug] {10, 0, 0, 1} is a reserved IP\n[debug] {1, 2, 3, 4} is a known client IP\n[debug] Processed remote IP, found client {1, 2, 3, 4} to replace {127, 0, 0, 1}\n```\n\nThis can help you narrow down the issue:\n\n* Do you see these logs at all? If not, `RemoteIp` might not even be called by your pipeline. Try debugging your code.\n* Are you getting the request headers you expect? Your particular proxies might not be sending the forwarding headers they should be.\n* Did you configure `:headers` right? `RemoteIp` only pays attention to the forwarding headers you specify.\n* Did you configure `:proxies` right? If you don't configure an IP as a known proxy, `RemoteIp` assumes it's a legitimate client.\n* Did you configure `:clients` right? Loopback or private IPs are automatically identified as proxies. If you need to carve out exceptions, you should add the relevant IP ranges to the list of known clients.\n* Are all the IPs being parsed correctly? `RemoteIp` will ignore values that it cannot parse. Either this is a bug in `RemoteIp` or a bad header.\n* Are the forwarding headers in the right order? IPs are processed last-to-first to prevent spoofing. Make sure you understand [the algorithm](extras/algorithm.md).\n* Are there multiple \"competing\" forwarding headers? The order we see the `req_headers` in the `Plug.Conn` matters for the last-to-first processing. Unfortunately, servers like [cowboy](https://github.com/ninenines/cowboy) can distort the order of incoming headers, since [Erlang maps](http://erlang.org/doc/man/maps.html) do not [preserve ordering](https://medium.com/@jlouis666/breaking-erlang-maps-1-31952b8729e6) (cf. [[1]](https://github.com/elixir-plug/plug_cowboy/blob/7bf68cd757c1a052e227112b681b77066fd84d2b/lib/plug/cowboy/conn.ex#L125-L127), [[2]](https://github.com/erlang/otp/blob/2c882ec2d504019f07104b3240a989148dfc1fa3/lib/stdlib/doc/src/maps.xml#L409)). You *might* be able to configure `RemoteIp` to avoid your particular problematic situation.\n\nIf none of the above apply, you may have found a bug in `RemoteIp`, so please go ahead and open an issue.\n\n### Other issues\n\nAll manner of issues are welcome. However, I don't often have much time to work on open source things, so my turnaround is usually pretty slow. You can help by giving as much context as possible:\n\n* :bug: Bugs\n  * How can it be reproduced?\n  * Do the logs help?\n  * What was the expected behavior?\n  * What was the actual behavior?\n* :sparkles: Feature requests\n  * What problem would it solve?\n  * How would it work?\n  * Why does it belong in this library?\n* :question: Questions\n  * Before asking why you're getting the wrong IP, do your [due diligence](#getting-the-wrong-ip).\n  * The more details you can provide, the better!\n\n## Pull Requests\n\nIf there's some bug you've fixed or feature you've implemented, contribute your changes through the usual means:\n\n1. Fork this project.\n2. Commit your changes.\n3. Open a pull request.\n\n### General guidelines\n\nA few notes about getting your pull request accepted:\n\n* [Write good commit messages.](https://chris.beams.io/posts/git-commit/)\n> **The seven rules of a great Git commit message**\n>\n> > Keep in mind: [This](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [has](https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines) [all](https://github.com/torvalds/subsurface-for-dirk/blob/master/README#L92-L120) [been](http://who-t.blogspot.co.at/2009/12/on-commit-messages.html) [said](https://github.com/erlang/otp/wiki/writing-good-commit-messages) [before](https://github.com/spring-projects/spring-framework/blob/30bce7/CONTRIBUTING.md#format-commit-messages).\n>\n> 1. Separate subject from body with a blank line\n> 2. Limit the subject line to 50 characters\n> 3. Capitalize the subject line\n> 4. Do not end the subject line with a period\n> 5. Use the imperative mood in the subject line\n> 6. Wrap the body at 72 characters\n> 7. Use the body to explain *what* and *why* vs. *how*\n* Keep the scope of your PR tight.\n  * **Do** make sure your PR accomplishes one specific thing.\n  * **Don't** make unnecessary or unrelated changes.\n* Keep your history clean.\n  * **Do** make sure each commit pertains conceptually to a single change.\n  * **Don't** leave your commits disorganized with various works-in-progress. [Rewrite](https://git-scm.com/book/id/v2/Git-Tools-Rewriting-History) [history](https://git-rebase.io/) [if](https://programmerfriend.com/git-best-practices/) [necessary](http://justinhileman.info/article/changing-history/).\n* Write a good PR description.\n  * What problem are you trying to solve?\n  * Who does the problem affect?\n  * When did this problem happen? Is it tied to a specific version?\n  * Where is the source of the issue? Is it an external project? Can you link to a relevant discussion?\n  * How did you solve it?\n  * Why is this the proper solution?\n* Write tests, if appropriate.\n* Proper documentation is appreciated.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 Alex Vondrak\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# RemoteIp\n\n[![build status](https://github.com/ajvondrak/remote_ip/workflows/build/badge.svg)](https://github.com/ajvondrak/remote_ip/actions?query=workflow%3Abuild)\n[![coverage status](https://coveralls.io/repos/github/ajvondrak/remote_ip/badge.svg?branch=main)](https://coveralls.io/github/ajvondrak/remote_ip?branch=main)\n[![hex.pm version](https://img.shields.io/hexpm/v/remote_ip)](https://hex.pm/packages/remote_ip)\n\nA [plug](https://github.com/elixir-lang/plug) to rewrite the [`Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html)'s `remote_ip` based on forwarding headers.\n\nGeneric comma-separated headers like `X-Forwarded-For`, `X-Real-Ip`, and `X-Client-Ip` are all recognized, as well as the [RFC 7239](https://tools.ietf.org/html/rfc7239) `Forwarded` header. You can specify any number of forwarding headers to recognize and even configure your own parsers.\n\nIPs are processed last-to-first to prevent IP spoofing. Loopback/private IPs are ignored by default, but known proxies & clients are configurable, so you have full control over which IPs are considered legitimate.\n\n**If your app is not behind at least one proxy, you should not use this plug.** See [the algorithm](extras/algorithm.md) for more details.\n\n## Installation\n\nAdd `:remote_ip` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:remote_ip, \"~> 1.2\"}]\nend\n```\n\n## Usage\n\nAdd the `RemoteIp` plug to your app's plug pipeline:\n\n```elixir\ndefmodule MyApp do\n  use Plug.Router\n\n  plug RemoteIp\n\n  plug :match\n  plug :dispatch\n\n  # get \"/\" do ...\nend\n```\n\nYou can also use `RemoteIp.from/2` outside of a plug pipeline to extract the remote IP from a list of headers:\n\n```elixir\nx_headers = [{\"x-forwarded-for\", \"1.2.3.4\"}]\nRemoteIp.from(x_headers)\n```\n\nSee the [documentation](https://hexdocs.pm/remote_ip) for full details on usage, configuration options, and troubleshooting.\n\n## Motivation\n\n### Problem: Your app is behind a proxy and you want to know the original client's IP address.\n\n[Proxies](https://en.wikipedia.org/wiki/Proxy_server) are pervasive for some purpose or another in modern HTTP infrastructure: encryption, load balancing, caching, compression, and more can be done via proxies. But a proxy makes HTTP requests appear to your app as if they came from the proxy's IP address. How is your app to know the \"actual\" requesting IP address (e.g., so you can geolocate a user)?\n\n**Solution:** Many proxies prevent this information loss by adding HTTP headers to communicate the requesting client's IP address. There is no single, universal header. Though [`X-Forwarded-For`](https://en.wikipedia.org/wiki/X-Forwarded-For) is common, options include [`X-Real-IP`](http://nginx.org/en/docs/http/ngx_http_realip_module.html), [`X-Client-IP`](http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html), and [others](http://stackoverflow.com/a/916157). Due to this lack of standardization, [RFC 7239](https://tools.ietf.org/html/rfc7239) defines the `Forwarded` header, fulfilling a [relevant XKCD truism](https://xkcd.com/927).\n\n### Problem: Plug does not derive `remote_ip` from headers such as `X-Forwarded-For`.\n\nPer the [`Plug.Conn` docs](https://hexdocs.pm/plug/Plug.Conn.html#module-request-fields):\n\n> * `remote_ip` - the IP of the client, example: `{151, 236, 219, 228}`. This\n>   field is meant to be overwritten by plugs that understand e.g. the\n>   `X-Forwarded-For` header or HAProxy's PROXY protocol. It defaults to peer's\n>   IP.\n\nNote that the field is _meant_ to be overwritten. Plug does not actually do any overwriting itself. The [Cowboy changelog](https://github.com/ninenines/cowboy/blob/master/CHANGELOG.md#084) espouses a similar platform of non-involvement:\n\n> Because each server's proxy situation differs, it is better that this function is implemented by the application directly.\n\n**Solution:** As definitively verified in [elixir-lang/plug#319](https://github.com/elixir-lang/plug/issues/319), users are intended to hand-roll their own header parsers.\n\n### Problem: Ain't nobody got time for that.\n\n**Solution:** There are a handful of plugs available on [Hex](https://hex.pm). There are also the comments left in the [elixir-lang/plug#319](https://github.com/elixir-lang/plug/issues/319) thread that may give you some ideas, but I consider them to be non-starters - copying/pasting code from github comments isn't much better than hand-rolling an implementation.\n\n### Problem: Existing solutions are incomplete and have subtle bugs.\n\nNone of the available solutions I have seen are ideal. In this sort of plug, you want:\n\n* **Configurable headers and parsers:** With so many different headers being used, you should be able to configure the ones you need and how to parse them.\n* **Configurable proxies and clients:** With multiple proxy hops, there may be several IPs in the forwarding headers. Without being able to tell the plug which of those IPs are actually known to be proxies, you may get one of them back as the `remote_ip`.\n* **Security and correctness:** Parsing forwarding headers can be surprisingly subtle, and it's easy to open yourself up to [IP spoofing](http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/) vulnerabilities.\n\nExisting packages all fail on one or more of these fronts:\n\n* [plug\\_cloudflare](https://hex.pm/packages/plug_cloudflare) - not a general purpose library, but is more secure & correct if you're specifically parsing the `CF-Connecting-IP`\n* [plug\\_forwarded\\_peer](https://hex.pm/packages/plug_forwarded_peer) - only parses `Forwarded` and `X-Forwarded-For` (`X-Forwarded-For` takes precedence over `Forwarded`), does not parse all of RFC 7239's supported syntax correctly, vulnerable to IP spoofing\n* [plug\\_x\\_forwarded\\_for](https://hex.pm/packages/plug_x_forwarded_for) - only configurable for a single header, all headers parsed the same as `X-Forwarded-For`, vulnerable to IP spoofing\n* [remote\\_ip\\_rewriter](https://hex.pm/packages/remote_ip_rewriter) - can only configure one header, all headers parsed the same as `X-Forwarded-For`, cannot configure loopback/private IPs as known clients\n* [trusted\\_proxy\\_rewriter](https://hex.pm/packages/trusted_proxy_rewriter) - outdated fork of remote\\_ip\\_rewriter, has even more limited functionality\n\n**Solution:** These are the sorts of things application developers should not have to worry about. `RemoteIp` aims to be the proper solution to all of these problems.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for instructions on how to open issues or pull requests.\n\n## Prior Art\n\nWhile `RemoteIp` has morphed into something distinct from the Rails middleware of the same name, the Rails code was definitely where I started. So I'd like to explicitly acknowledge the inspiration: this plug would not have been possible without poring over the existing implementation, discussions, documentation, and commits that went into the Rails code. :heart:\n\nRequired reading for anyone who wants to think way too much about forwarding headers:\n\n* [@gingerlime](https://github.com/gingerlime)'s [blog post](http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/) explaining IP spoofing\n* Rails' [`RemoteIp` middleware](https://github.com/rails/rails/blob/v4.1.4/actionpack/lib/action_dispatch/middleware/remote_ip.rb)\n* Rails' [tests](https://github.com/rails/rails/blob/92703a9ea5d8b96f30e0b706b801c9185ef14f0e/actionpack/test/dispatch/request_test.rb#L62)\n* The extensive discussion on [rails/rails#7980](https://github.com/rails/rails/pull/7980)\n"
  },
  {
    "path": "bench/.formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "bench/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nbench-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n"
  },
  {
    "path": "bench/README.md",
    "content": "# Benchmarks\n\nFor the purposes of remote\\_ip, we need a library to (1) parse strings from CIDR notation into a usable representation for (2) checking if an IP falls within a certain block. At the time of this writing, there are a couple [CIDR libraries](https://hex.pm/packages?search=cidr) available on Hex.pm:\n\n* [inet\\_cidr](https://hex.pm/packages/inet_cidr)\n* [erl\\_cidr](https://hex.pm/packages/erl_cidr) (an Erlang wrapper around inet\\_cidr)\n* [cidr](https://hex.pm/packages/cidr)\n* [cider](https://hex.pm/packages/cider)\n\nDue to the shortcomings of these libraries, remote\\_ip rolls its own `RemoteIp.Block` module. This app serves as a testing ground for comparing remote\\_ip's implementation against the others to validate whether it's actually an improvement.\n\n## Results\n\nUsing [benchee](https://github.com/bencheeorg/benchee), we run a block of code repeatedly for a static amount of time (after a warmup) and count how many iterations we got through. Thus, in the _iterations per second_ results charted below, bigger is better: it means we performed more operations in a given amount of time.\n\n### Parsing CIDRs\n\nThis benchmark generates 1,000 random CIDR strings and measures the time it takes to parse them all with each different library.\n\n![A data plot for the CIDR parsing benchmarks](img/parse.png)\n\n```console\n$ mix run parse.exs\nRandomizing with seed 294598\n\nOperating System: macOS\nCPU Information: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz\nNumber of Available Cores: 8\nAvailable memory: 16 GB\nElixir 1.11.4\nErlang 23.2.7\n\nBenchmark suite executing with the following configuration:\nwarmup: 2 s\ntime: 5 s\nmemory time: 0 ns\nparallel: 1\ninputs: none specified\nEstimated total run time: 28 s\n\nBenchmarking cider...\nBenchmarking cidr...\nBenchmarking inet_cidr...\nBenchmarking remote_ip...\nGenerated tmp/parse.html\nGenerated tmp/parse_cider.html\nGenerated tmp/parse_cidr.html\nGenerated tmp/parse_comparison.html\nGenerated tmp/parse_inet_cidr.html\nGenerated tmp/parse_remote_ip.html\n\nName                ips        average  deviation         median         99th %\ncider            269.94        3.70 ms     ±7.72%        3.61 ms        4.74 ms\nremote_ip        264.49        3.78 ms     ±9.78%        3.70 ms        5.22 ms\ninet_cidr        243.27        4.11 ms     ±9.25%        4.08 ms        5.51 ms\ncidr             169.52        5.90 ms    ±11.24%        5.77 ms        7.79 ms\n\nComparison:\ncider            269.94\nremote_ip        264.49 - 1.02x slower +0.0763 ms\ninet_cidr        243.27 - 1.11x slower +0.41 ms\ncidr             169.52 - 1.59x slower +2.19 ms\n```\n\n### Checking IPs\n\nThis benchmark generates 1,000 random CIDRs and 1,000 randoms IPs, then uses each library to check every combination (1,000 CIDRs x 1,000 IPs = 1,000,000 checks per iteration).\n\nTo avoid conflating the performance results, we perform the CIDR parsing ahead of time and don't measure it in the actual benchmark. Similarly, both remote\\_ip and cider must encodes the incoming IPs as integers, so we perform that step outside of the measurement as well.\n\n![A data plot for the IP checking benchmarks](img/check.png)\n\n```console\n$ mix run check.exs\nRandomizing with seed 570890\n\nOperating System: macOS\nCPU Information: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz\nNumber of Available Cores: 8\nAvailable memory: 16 GB\nElixir 1.11.4\nErlang 23.2.7\n\nBenchmark suite executing with the following configuration:\nwarmup: 2 s\ntime: 5 s\nmemory time: 0 ns\nparallel: 1\ninputs: none specified\nEstimated total run time: 28 s\n\nBenchmarking cider...\nBenchmarking cidr...\nBenchmarking inet_cidr...\nBenchmarking remote_ip...\nGenerated tmp/check.html\nGenerated tmp/check_cider.html\nGenerated tmp/check_cidr.html\nGenerated tmp/check_comparison.html\nGenerated tmp/check_inet_cidr.html\nGenerated tmp/check_remote_ip.html\n\nName                ips        average  deviation         median         99th %\nremote_ip         14.60       68.50 ms     ±1.43%       68.65 ms       70.67 ms\ncider             10.68       93.66 ms     ±2.41%       93.86 ms       97.84 ms\ninet_cidr          6.37      156.88 ms     ±2.89%      157.15 ms      165.46 ms\ncidr               1.53      652.73 ms     ±0.91%      652.71 ms      661.14 ms\n\nComparison:\nremote_ip         14.60\ncider             10.68 - 1.37x slower +25.16 ms\ninet_cidr          6.37 - 2.29x slower +88.38 ms\ncidr               1.53 - 9.53x slower +584.23 ms\n```\n\n## Other considerations\n\nPerformance is a nice, objective nail in the coffin for these other libraries. But they also have other issues that make them impractical to use:\n\n* inet\\_cidr doesn't parse individual IP addresses, so instead of `\"1.2.3.4\"` you have to say `\"1.2.3.4/32\"`\n* cidr's `CIDR.match/2` interface is awkward compared to the boolean `contains?/2` functions provided by everyone else\n* cider fails to distinguish between IPv4 and IPv6 addresses, so it produces false positives like\n    ```elixir\n    iex> Cider.contains?({0, 0, 0, 1}, Cider.parse(\"::1\"))\n    true\n    ```\n\nremote\\_ip's implementation neatly handles all of these limitations while also performing significantly better.\n"
  },
  {
    "path": "bench/check.exs",
    "content": "Bench.Inputs.seed\n\nips = Bench.Inputs.ips(1_000)\n\nparsed_ips = %{\n  remote_ip: Enum.map(ips, &RemoteIp.Block.encode/1),\n  inet_cidr: ips,\n  cider: Enum.map(ips, &Cider.ip!/1),\n  cidr: ips,\n}\n\ncidrs = Bench.Inputs.cidrs(1_000)\n\nparsed_cidrs = %{\n  remote_ip: Enum.map(cidrs, &RemoteIp.Block.parse!/1),\n  inet_cidr: Enum.map(cidrs, &InetCidr.parse_cidr!(&1, true)),\n  cider: Enum.map(cidrs, &Cider.parse/1),\n  cidr: Enum.map(cidrs, &CIDR.parse/1),\n}\n\nsuite = %{\n  remote_ip: fn ->\n    Enum.each(parsed_ips[:remote_ip], fn ip ->\n      Enum.each(parsed_cidrs[:remote_ip], &RemoteIp.Block.contains?(&1, ip))\n    end)\n  end,\n  inet_cidr: fn ->\n    Enum.each(parsed_ips[:inet_cidr], fn ip ->\n      Enum.each(parsed_cidrs[:inet_cidr], &InetCidr.contains?(&1, ip))\n    end)\n  end,\n  cider: fn ->\n    Enum.each(parsed_ips[:cider], fn ip ->\n      Enum.each(parsed_cidrs[:cider], &Cider.contains?(ip, &1))\n    end)\n  end,\n  cidr: fn ->\n    Enum.each(parsed_ips[:cidr], fn ip ->\n      Enum.each(parsed_cidrs[:cidr], &CIDR.match(&1, ip))\n    end)\n  end,\n}\n\nformatters = [\n  {Benchee.Formatters.HTML, file: \"tmp/check.html\", auto_open: false},\n  Benchee.Formatters.Console,\n]\n\nBenchee.run(suite, formatters: formatters)\n"
  },
  {
    "path": "bench/lib/bench/inputs.ex",
    "content": "defmodule Bench.Inputs do\n  def seed do\n    seed =\n      case System.fetch_env(\"SEED\") do\n        {:ok, var} -> String.to_integer(var)\n        :error -> System.system_time(:microsecond) |> rem(1_000_000)\n      end\n\n    IO.puts(\"Randomizing with seed #{seed}\\n\")\n    :rand.seed(:exs1024, {seed, seed, seed})\n  end\n\n  def cidrs(n) do\n    Stream.repeatedly(fn -> cidr() end) |> Enum.take(n)\n  end\n\n  def cidr do\n    case Enum.random([:ipv4, :ipv6]) do\n      :ipv4 -> cidr(ipv4())\n      :ipv6 -> cidr(ipv6())\n    end\n  end\n\n  def cidr({a, b, c, d}) do\n    cidr({a, b, c, d}, Enum.random(0..32))\n  end\n\n  def cidr({a, b, c, d, e, f, g, h}) do\n    cidr({a, b, c, d, e, f, g, h}, Enum.random(0..128))\n  end\n\n  def cidr(ip, prefix) do\n    \"#{:inet.ntoa(ip)}/#{prefix}\"\n  end\n\n  def ips(n) do\n    Stream.repeatedly(fn -> ip() end) |> Enum.take(n)\n  end\n\n  def ip do\n    case Enum.random([:ipv4, :ipv6]) do\n      :ipv4 -> ipv4()\n      :ipv6 -> ipv6()\n    end\n  end\n\n  def ipv4 do\n    Stream.repeatedly(fn -> Enum.random(0..255) end)\n    |> Enum.take(4)\n    |> List.to_tuple()\n  end\n\n  def ipv6 do\n    Stream.repeatedly(fn -> Enum.random(0..0xFFFF) end)\n    |> Enum.take(8)\n    |> List.to_tuple()\n  end\nend\n"
  },
  {
    "path": "bench/mix.exs",
    "content": "defmodule Bench.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :bench,\n      version: \"0.0.0\",\n      elixir: \"~> 1.12\",\n      deps: deps()\n    ]\n  end\n\n  def application do\n    [\n      extra_applications: [:logger]\n    ]\n  end\n\n  defp deps do\n    [\n      {:benchee, \"~> 1.3\"},\n      {:benchee_html, \"~> 1.0\"},\n      {:inet_cidr, \"~> 1.0\"},\n      {:cidr, \"~> 1.0\"},\n      {:cider, \"~> 0.3\"},\n      {:remote_ip, path: \"..\"}\n    ]\n  end\nend\n"
  },
  {
    "path": "bench/parse.exs",
    "content": "Bench.Inputs.seed\n\ncidrs = Bench.Inputs.cidrs(1_000)\n\nsuite = %{\n  remote_ip: fn -> Enum.each(cidrs, &RemoteIp.Block.parse!/1) end,\n  inet_cidr: fn -> Enum.each(cidrs, &InetCidr.parse_cidr!(&1, true)) end,\n  cider: fn -> Enum.each(cidrs, &Cider.parse/1) end,\n  cidr: fn -> Enum.each(cidrs, &CIDR.parse/1) end,\n}\n\nformatters = [\n  {Benchee.Formatters.HTML, file: \"tmp/parse.html\", auto_open: false},\n  Benchee.Formatters.Console,\n]\n\nBenchee.run(suite, formatters: formatters)\n"
  },
  {
    "path": "coveralls.json",
    "content": "{\n  \"skip_files\": [\n    \"lib/remote_ip/debugger.ex\"\n  ],\n  \"coverage_options\": {\n    \"minimum_coverage\": 100\n  }\n}\n"
  },
  {
    "path": "extras/algorithm.md",
    "content": "# Algorithm\n\nTo avoid IP spoofing vulnerabilities, `RemoteIp` employs a very particular algorithm. Its work is divided into two main phases:\n\n1. Parse the right `req_headers`.\n2. Compute the right `remote_ip`.\n\nWe will analyze these steps in detail to understand the benefits and caveats of the algorithm. Much of it relies on configuration values given by `RemoteIp.Options`. You may also log the steps of this algorithm using the `RemoteIp.Debugger`.\n\nAs a running example, consider the following request route:\n\n* Client at IP `1.2.3.4` sends an HTTP request to Proxy 1 (no forwarding headers)\n* Proxy 1 at IP `1.1.1.1` adds an `X-Forwarded-For: 1.2.3.4` header and forwards to Proxy 2\n* Proxy 2 at IP `2.2.2.2` adds Proxy 1 to the header with `X-Forwarded-For: 1.2.3.4, 1.1.1.1` and forwards to Proxy 3\n* Proxy 3 at IP `3.3.3.3` adds a `Forwarded: for=2.2.2.2` header and forwards to the application\n* Application receives the request from IP `3.3.3.3` with forwarding headers `X-Forwarded-For: 1.2.3.4, 1.1.1.1` and `Forwarded: for=2.2.2.2`\n\n## Parsing headers\n\nThere are many different forwarding headers in the wild, including `Forwarded`, `X-Forwarded-For`, `X-Client-Ip`, and `X-Real-Ip`. The header that gets used depends on the configuration of the proxy your app sits behind. If there are multiple proxies in play, it's conceivable for you to have more than one such header.\n\nThe `:headers` option tells `RemoteIp` which specific headers to parse for IP addresses. The default value casts a wide net, but you should ideally specify only those headers which you're certain you require. Otherwise, it would be trivial for a malicious client to add an extra header that could interfere with finding the correct IP.\n\nTo start off the algorithm, all of the configured headers are taken from the `Plug.Conn`'s `req_headers`. Their relative ordering is maintained, because order matters when there are multiple hops between proxies. In our running example, assuming both `Forwarded` and `X-Forwarded-For` were in the `:headers` option (which they are by default), we want to process the list\n\n```elixir\n# This is what we want\n[{\"x-forwarded-for\", \"1.2.3.4, 1.1.1.1\"}, {\"forwarded\", \"for=2.2.2.2\"}]\n```\n\nand *not* the reverse\n\n```elixir\n# This is NOT what we want\n[{\"forwarded\", \"for=2.2.2.2\"}, {\"x-forwarded-for\", \"1.2.3.4, 1.1.1.1\"}]\n```\n\nLet's assume we get the former. In reality, however, we usually can't rely on the stable ordering of headers in an HTTP request. For example, the [Cowboy](https://github.com/ninenines/cowboy/) server presently [uses maps](https://github.com/elixir-plug/plug_cowboy/blob/f82f2ff982f04fb4faa3a12fd2b08a7cc56ebe15/lib/plug/cowboy/conn.ex#L125-L127) to represent headers, which don't preserve key order, so everything could get jumbled up.\n\nConfiguring multiple headers might still be useful if, for example, you expect some requests to only have header A and other requests to only have header B, but never both at the same time. So `RemoteIp` doesn't limit you to just the one choice.\n\nAfter selecting the allowed headers, each string is parsed for its IP addresses. In the common case, we parse comma-separated IPs with `RemoteIp.Parsers.Generic`. This works for headers like `X-Forwarded-For`, `X-Client-Ip` and `X-Real-Ip`. But you can also configure custom parsers using the `:parsers` option. For instance, by default we include `RemoteIp.Parsers.Forwarded` to parse the format specified by RFC 7239.\n\nEach parser returns a list of IPs, each of the [`:inet.ip_address/0` type](http://erlang.org/doc/man/inet.html#type-ip_address). If there were any errors (e.g., a malformed header), this should be an empty list. But any one header may also specify multiple IPs, so once again it's important that the relative order is maintained. Thus, in our running example, the `X-Forwarded-For` header should parse as\n\n```elixir\n# This is what we want\n[{1, 2, 3, 4}, {1, 1, 1, 1}]\n```\n\nand *not* another order like\n\n```elixir\n# This is NOT what we want\n[{1, 1, 1, 1}, {1, 2, 3, 4}]\n```\n\nThe lists returned by each parser are then concatenated together to form one chain of IPs. In our running example, the resulting addresses are\n\n```elixir\n[{1, 2, 3, 4}, {1, 1, 1, 1}, {2, 2, 2, 2}]\n```\n\n## Finding the client\n\nWith the list of IPs parsed, `RemoteIp` must then calculate the proper `remote_ip`. To [prevent IP spoofing](http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/), IPs are processed right-to-left. You can think of it as going backwards through the chain of hops:\n\n1. Application is receiving a request from Proxy 3\n2. The Proxy 3 to Application hop set the header `Forwarded: for=2.2.2.2`\n3. The Proxy 2 to Proxy 3 hop added `1.1.1.1` to the `X-Forwarded-For` header\n4. The Proxy 1 to Proxy 2 hop set the `X-Forwarded-For: 1.2.3.4` header\n5. Client is sending a request to Proxy 1\n\nWe work backwards until we find something that looks like a client IP. This is dictated by the `:proxies` option, which configures the list of known proxy IPs. Any IP that is *not* a known proxy is assumed to be a client. In our running example:\n\n1. The peer address `{3, 3, 3, 3}` is automatically assumed to be a proxy IP, so go through the headers\n2. `{2, 2, 2, 2}` is a known proxy IP, so go one hop back\n3. `{1, 1, 1, 1}` is a known proxy IP, so go one hop back\n4. `{1, 2, 3, 4}` is not a known proxy IP, so we assume it's the client\n\nNotice that the peer address is *always* assumed to be \"wrong\". Therefore, you should not use `RemoteIp` unless your app is behind at least one proxy. Otherwise, it would be trivial for a malicious client to spoof their IP address: if they just set a header themselves, we'll automatically use it to rewrite the `Plug.Conn`'s original (correct) peer address.\n\nIt's also important to go *backwards* through the chain, or else the client could similarly spoof their IP. For instance, consider if the client in the running example had initially sent the header `Forwarded: for=6.7.8.9`. Then the headers would have come in as\n\n```elixir\n[{\"forwarded\", \"for=6.7.8.9\"}, {\"x-forwarded-for\", \"1.2.3.4, 1.1.1.1\"}, {\"forwarded\", \"for=2.2.2.2\"}]\n```\n\nwhich would parse out as the IPs\n\n```elixir\n[{6, 7, 8, 9}, {1, 2, 3, 4}, {1, 1, 1, 1}, {2, 2, 2, 2}]\n```\n\nIf we were to go *forwards* through this list, we'd immediately return `{6, 7, 8, 9}` as the client IP, even though it was being spoofed by our malicious user. Instead, going backwards still gives us `{1, 2, 3, 4}` even though the client is attempting to spoof the IP with their own headers. This works no matter how many extra headers the client sends.\n\nThis logic generalizes to any bad actors in the middle of the chain, too. If we add an IP to the `:proxies` list, we're trusting the forwarding headers that it sets. As such, we're implicitly trusting the incoming peer address, even without configuring it. So in our running example, it's impossible not to trust Proxy 3. We believe it when it says the request came from Proxy 2. But if we didn't trust Proxy 2, that's where we stop: we say the client is `{2, 2, 2, 2}` and won't dig further because we don't trust the `X-Forwarded-For` header that came from Proxy 2.\n\nNot only are known proxies' headers trusted, but also requests forwarded for [loopback](https://en.wikipedia.org/wiki/Loopback) and [private](https://en.wikipedia.org/wiki/Private_network) IPs:\n\n* IPv4 loopback - `127.0.0.0/8`\n* IPv6 loopback - `::1/128`\n* IPv4 private network - `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n* IPv6 unique local address - `fc00::/7`\n\nThese IPs are skipped automatically because they are used internally and are thus generally not the actual client address in production. However, if (say) your app is only deployed in a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network)/[LAN](https://en.wikipedia.org/wiki/Local_area_network), then your clients might actually have these internal IPs. To prevent loopback/private addresses from being considered proxies, configure them as known clients using the `:clients` option. This goes for anything you have listed in `:proxies` as well. For example, you might say that a whole CIDR block belongs to proxies, but then carve out an exception for a single client in that block.\n"
  },
  {
    "path": "integration/tests/basic/.formatter.exs",
    "content": "[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"],\n  import_deps: [:plug]\n]\n"
  },
  {
    "path": "integration/tests/basic/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nbasic-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n"
  },
  {
    "path": "integration/tests/basic/README.md",
    "content": "# Basic integration test\n\nThis app plugs `RemoteIp` into a simple `Plug.Router` pipeline. Sans configuration, we expect remote\\_ip to have been compiled without any debug logs. An app this straightforward serves as a basic smoke test for standard usage of the plug.\n"
  },
  {
    "path": "integration/tests/basic/lib/basic.ex",
    "content": "defmodule Basic do\n  use Plug.Router\n\n  plug RemoteIp\n  plug :match\n  plug :dispatch\n\n  get \"/ip\" do\n    send_resp(conn, 200, :inet.ntoa(conn.remote_ip))\n  end\nend\n"
  },
  {
    "path": "integration/tests/basic/mix.exs",
    "content": "defmodule Basic.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :basic,\n      version: \"0.0.0\",\n      elixir: \"~> 1.12\",\n      deps: [remote_ip: [path: \"../../..\"]]\n    ]\n  end\n\n  def application do\n    [extra_applications: [:logger]]\n  end\nend\n"
  },
  {
    "path": "integration/tests/basic/test/basic_test.exs",
    "content": "defmodule BasicTest do\n  use ExUnit.Case\n  use Plug.Test\n\n  import ExUnit.CaptureLog\n\n  def xff(conn, header) do\n    put_req_header(conn, \"x-forwarded-for\", header)\n  end\n\n  def call(conn, opts \\\\ []) do\n    Basic.call(conn, Basic.init(opts))\n  end\n\n  test \"GET /ip\" do\n    conn = conn(:get, \"/ip\") |> xff(\"1.2.3.4,2.3.4.5,127.0.0.1\")\n    assert \"\" == capture_log(fn -> assert call(conn).resp_body == \"2.3.4.5\" end)\n  end\nend\n"
  },
  {
    "path": "integration/tests/basic/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "integration/tests/custom/.formatter.exs",
    "content": "[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "integration/tests/custom/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\ncustom-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n"
  },
  {
    "path": "integration/tests/custom/README.md",
    "content": "# Custom integration test\n\nThis app customizes the subset of debug messages it wants remote\\_ip to actually log. All the others should be removed at compile time. We test this by directly calling `RemoteIp.call/2` & `RemoteIp.from/2` to inspect their log output.\n\nThis doesn't enumerate all the possible customizations or anything, but it does provide a smoke test. Here, we log the parsed IPs and the resulting remote IP.\n\n```elixir\nconfig :remote_ip, debug: [:ips, :ip]\n```\n"
  },
  {
    "path": "integration/tests/custom/config/config.exs",
    "content": "import Config\n\nconfig :logger, :console,\n  colors: [enabled: false],\n  format: \"[$level] $message\\n\"\n\nconfig :remote_ip, debug: [:ips, :ip]\n"
  },
  {
    "path": "integration/tests/custom/mix.exs",
    "content": "defmodule Custom.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :custom,\n      version: \"0.0.0\",\n      elixir: \"~> 1.12\",\n      deps: [remote_ip: [path: \"../../..\"]]\n    ]\n  end\n\n  def application do\n    [extra_applications: [:logger]]\n  end\nend\n"
  },
  {
    "path": "integration/tests/custom/test/custom_test.exs",
    "content": "defmodule CustomTest do\n  use ExUnit.Case\n  import ExUnit.CaptureLog\n\n  @head [\n    {\"user-agent\", \"test\"},\n    {\"x-forwarded-for\", \"3.14.15.9, 26.53.58.97, 93.238.46.26\"}\n  ]\n\n  @conn %Plug.Conn{\n    remote_ip: {127, 0, 0, 1},\n    req_headers: @head\n  }\n\n  def call(conn, opts \\\\ []) do\n    RemoteIp.call(conn, RemoteIp.init(opts))\n  end\n\n  def from(head, opts \\\\ []) do\n    RemoteIp.from(head, opts)\n  end\n\n  test \"hit with RemoteIp.call/2\" do\n    assert capture_log(fn -> call(@conn) end) == \"\"\"\n           [debug] Parsed IPs from forwarding headers: [{3, 14, 15, 9}, {26, 53, 58, 97}, {93, 238, 46, 26}]\n           [debug] Processed remote IP, found client {93, 238, 46, 26} to replace {127, 0, 0, 1}\n           \"\"\"\n  end\n\n  test \"miss with RemoteIp.call/2\" do\n    assert capture_log(fn -> call(@conn, headers: []) end) == \"\"\"\n           [debug] Parsed IPs from forwarding headers: []\n           [debug] Processed remote IP, no client found to replace {127, 0, 0, 1}\n           \"\"\"\n  end\n\n  test \"hit with RemoteIp.from/2\" do\n    assert capture_log(fn -> from(@head) end) == \"\"\"\n           [debug] Parsed IPs from forwarding headers: [{3, 14, 15, 9}, {26, 53, 58, 97}, {93, 238, 46, 26}]\n           [debug] Processed remote IP, found client {93, 238, 46, 26}\n           \"\"\"\n  end\n\n  test \"miss with RemoteIp.from/2\" do\n    assert capture_log(fn -> from(@head, headers: []) end) == \"\"\"\n           [debug] Parsed IPs from forwarding headers: []\n           [debug] Processed remote IP, no client found\n           \"\"\"\n  end\nend\n"
  },
  {
    "path": "integration/tests/custom/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "integration/tests/debug/.formatter.exs",
    "content": "[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "integration/tests/debug/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\ndebug-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n"
  },
  {
    "path": "integration/tests/debug/README.md",
    "content": "# Debug integration test\n\nThis app compiles remote\\_ip with the configuration\n\n```elixir\nconfig :remote_ip, debug: true\n```\n\nand calls `RemoteIp.call/2` & `RemoteIp.from/2` directly to inspect their debug logs on some basic examples. This gives us coverage on all the possible types of debug messages across the remote\\_ip codebase.\n"
  },
  {
    "path": "integration/tests/debug/config/config.exs",
    "content": "import Config\n\nconfig :logger, :console,\n  colors: [enabled: false],\n  format: \"[$level] $message\\n\"\n\nconfig :remote_ip, debug: true\n"
  },
  {
    "path": "integration/tests/debug/mix.exs",
    "content": "defmodule Debug.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :debug,\n      version: \"0.0.0\",\n      elixir: \"~> 1.12\",\n      deps: [remote_ip: [path: \"../../..\"]]\n    ]\n  end\n\n  def application do\n    [extra_applications: [:logger]]\n  end\nend\n"
  },
  {
    "path": "integration/tests/debug/test/debug_test.exs",
    "content": "defmodule DebugTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  @head [\n    {\"accept\", \"*/*\"},\n    {\"x-forwarded-for\", \"3.14.15.9\"},\n    {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}\n  ]\n\n  @conn %Plug.Conn{\n    remote_ip: {127, 0, 0, 1},\n    req_headers: @head\n  }\n\n  def call(opts) do\n    RemoteIp.call(@conn, RemoteIp.init(opts))\n  end\n\n  def from(opts) do\n    RemoteIp.from(@head, opts)\n  end\n\n  describe \"RemoteIp.call/2\" do\n    test \"no client\" do\n      opts = [\n        headers: ~w[xff],\n        proxies: ~w[1.2.0.0/16 2.3.4.5/32],\n        clients: ~w[]\n      ]\n\n      assert capture_log(fn -> call(opts) end) == \"\"\"\n             [debug] Processing remote IP\n               headers: [\"xff\"]\n               parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n               proxies: [\"1.2.0.0/16\", \"2.3.4.5/32\"]\n               clients: []\n             [debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsing IPs from forwarding headers: [{\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsed IPs from forwarding headers: [{1, 2, 3, 4}, {10, 0, 0, 1}, {2, 3, 4, 5}]\n             [debug] {2, 3, 4, 5} is a known proxy IP\n             [debug] {10, 0, 0, 1} is a reserved IP\n             [debug] {1, 2, 3, 4} is a known proxy IP\n             [debug] Processed remote IP, no client found to replace {127, 0, 0, 1}\n             \"\"\"\n    end\n\n    test \"known client\" do\n      opts = [\n        headers: ~w[xff],\n        proxies: ~w[1.2.0.0/16 2.3.4.5/32],\n        clients: ~w[1.2.3.4/32]\n      ]\n\n      assert capture_log(fn -> call(opts) end) == \"\"\"\n             [debug] Processing remote IP\n               headers: [\"xff\"]\n               parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n               proxies: [\"1.2.0.0/16\", \"2.3.4.5/32\"]\n               clients: [\"1.2.3.4/32\"]\n             [debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsing IPs from forwarding headers: [{\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsed IPs from forwarding headers: [{1, 2, 3, 4}, {10, 0, 0, 1}, {2, 3, 4, 5}]\n             [debug] {2, 3, 4, 5} is a known proxy IP\n             [debug] {10, 0, 0, 1} is a reserved IP\n             [debug] {1, 2, 3, 4} is a known client IP\n             [debug] Processed remote IP, found client {1, 2, 3, 4} to replace {127, 0, 0, 1}\n             \"\"\"\n    end\n\n    test \"assumed client\" do\n      opts = [\n        headers: ~w[xff],\n        proxies: ~w[2.3.4.5/32],\n        clients: ~w[]\n      ]\n\n      assert capture_log(fn -> call(opts) end) == \"\"\"\n             [debug] Processing remote IP\n               headers: [\"xff\"]\n               parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n               proxies: [\"2.3.4.5/32\"]\n               clients: []\n             [debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsing IPs from forwarding headers: [{\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsed IPs from forwarding headers: [{1, 2, 3, 4}, {10, 0, 0, 1}, {2, 3, 4, 5}]\n             [debug] {2, 3, 4, 5} is a known proxy IP\n             [debug] {10, 0, 0, 1} is a reserved IP\n             [debug] {1, 2, 3, 4} is an unknown IP, assuming it's the client\n             [debug] Processed remote IP, found client {1, 2, 3, 4} to replace {127, 0, 0, 1}\n             \"\"\"\n    end\n  end\n\n  describe \"RemoteIp.from/2\" do\n    test \"no client\" do\n      opts = [\n        headers: ~w[],\n        proxies: ~w[1.2.0.0/16 2.3.4.5/32],\n        clients: ~w[1.0.0.0/8 2.0.0.0/8 3.0.0.0/8]\n      ]\n\n      assert capture_log(fn -> from(opts) end) == \"\"\"\n             [debug] Processing remote IP\n               headers: []\n               parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n               proxies: [\"1.2.0.0/16\", \"2.3.4.5/32\"]\n               clients: [\"1.0.0.0/8\", \"2.0.0.0/8\", \"3.0.0.0/8\"]\n             [debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsing IPs from forwarding headers: []\n             [debug] Parsed IPs from forwarding headers: []\n             [debug] Processed remote IP, no client found\n             \"\"\"\n    end\n\n    test \"known client\" do\n      opts = [\n        headers: ~w[x-forwarded-for],\n        proxies: ~w[1.2.0.0/16 2.3.4.5/32],\n        clients: ~w[3.0.0.0/8]\n      ]\n\n      assert capture_log(fn -> from(opts) end) == \"\"\"\n             [debug] Processing remote IP\n               headers: [\"x-forwarded-for\"]\n               parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n               proxies: [\"1.2.0.0/16\", \"2.3.4.5/32\"]\n               clients: [\"3.0.0.0/8\"]\n             [debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsing IPs from forwarding headers: [{\"x-forwarded-for\", \"3.14.15.9\"}]\n             [debug] Parsed IPs from forwarding headers: [{3, 14, 15, 9}]\n             [debug] {3, 14, 15, 9} is a known client IP\n             [debug] Processed remote IP, found client {3, 14, 15, 9}\n             \"\"\"\n    end\n\n    test \"assumed client\" do\n      opts = [\n        headers: ~w[x-forwarded-for xff],\n        proxies: ~w[2.3.4.5/32 3.0.0.0/8],\n        clients: ~w[]\n      ]\n\n      assert capture_log(fn -> from(opts) end) == \"\"\"\n             [debug] Processing remote IP\n               headers: [\"x-forwarded-for\", \"xff\"]\n               parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n               proxies: [\"2.3.4.5/32\", \"3.0.0.0/8\"]\n               clients: []\n             [debug] Taking forwarding headers from [{\"accept\", \"*/*\"}, {\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsing IPs from forwarding headers: [{\"x-forwarded-for\", \"3.14.15.9\"}, {\"xff\", \"1.2.3.4, 10.0.0.1, 2.3.4.5\"}]\n             [debug] Parsed IPs from forwarding headers: [{3, 14, 15, 9}, {1, 2, 3, 4}, {10, 0, 0, 1}, {2, 3, 4, 5}]\n             [debug] {2, 3, 4, 5} is a known proxy IP\n             [debug] {10, 0, 0, 1} is a reserved IP\n             [debug] {1, 2, 3, 4} is an unknown IP, assuming it's the client\n             [debug] Processed remote IP, found client {1, 2, 3, 4}\n             \"\"\"\n    end\n  end\nend\n"
  },
  {
    "path": "integration/tests/debug/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "integration/tests/parsers/.formatter.exs",
    "content": "[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"],\n  import_deps: [:plug]\n]\n"
  },
  {
    "path": "integration/tests/parsers/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nparsers-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n"
  },
  {
    "path": "integration/tests/parsers/README.md",
    "content": "# Parsers integration test\n\nThis app recognizes a custom header named `\"forwarding\"` which is parsed with the app's own custom implementation of the `RemoteIp.Parser` behaviour.\n\nThe header is completely made up. Its format is\n\n```\ntype=ip\n```\n\nwhere\n\n* `type` is either `proxy` or `client`\n* `ip` is a valid IP address\n\nOf course, you wouldn't expect to rely on the header _telling_ you if an IP was a proxy. Bad actors could easily spoof the header (at least if it's plaintext like this). In the real world, you'd configure the `RemoteIp` plug. But this format makes for more interesting tests.\n\nThis is an integration test so that we can compile remote\\_ip with debugging enabled, then make sure that the custom `:parsers` option gets logged.\n"
  },
  {
    "path": "integration/tests/parsers/config/config.exs",
    "content": "import Config\n\nconfig :logger, :console,\n  colors: [enabled: false],\n  format: \"[$level] $message\\n\"\n\nconfig :remote_ip, debug: [:options, :ips]\n"
  },
  {
    "path": "integration/tests/parsers/lib/parsers/forwarding.ex",
    "content": "defmodule Parsers.Forwarding do\n  @behaviour RemoteIp.Parser\n\n  @impl RemoteIp.Parser\n\n  def parse(value) do\n    [type, address] = String.split(value, \"=\")\n\n    case type do\n      \"proxy\" ->\n        []\n\n      \"client\" ->\n        {:ok, ip} = :inet.parse_strict_address(address |> to_charlist())\n        [ip]\n    end\n  end\nend\n"
  },
  {
    "path": "integration/tests/parsers/lib/parsers.ex",
    "content": "defmodule Parsers do\n  use Plug.Router\n\n  plug RemoteIp,\n    headers: ~w[forwarding],\n    parsers: %{\"forwarding\" => Parsers.Forwarding}\n\n  plug :match\n  plug :dispatch\n\n  get \"/ip\" do\n    send_resp(conn, 200, :inet.ntoa(conn.remote_ip))\n  end\nend\n"
  },
  {
    "path": "integration/tests/parsers/mix.exs",
    "content": "defmodule Parsers.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :parsers,\n      version: \"0.0.0\",\n      elixir: \"~> 1.12\",\n      deps: [remote_ip: [path: \"../../..\"]]\n    ]\n  end\n\n  def application do\n    [extra_applications: [:logger]]\n  end\nend\n"
  },
  {
    "path": "integration/tests/parsers/test/parsers_test.exs",
    "content": "defmodule ParsersTest do\n  use ExUnit.Case\n  use Plug.Test\n\n  import ExUnit.CaptureLog\n\n  def call(conn, opts \\\\ []) do\n    Parsers.call(conn, Parsers.init(opts))\n  end\n\n  test \"GET /ip\" do\n    head = [\n      {\"forwarding\", \"client=1.2.3.4\"},\n      {\"forwarding\", \"proxy=10.20.30.40\"},\n      {\"forwarding\", \"client=2.3.4.5\"},\n      {\"forwarding\", \"proxy=20.30.40.50\"}\n    ]\n\n    conn = %{conn(:get, \"/ip\") | req_headers: head}\n\n    logs = capture_log(fn -> assert call(conn).resp_body == \"2.3.4.5\" end)\n\n    assert logs == \"\"\"\n           [debug] Processing remote IP\n             headers: [\"forwarding\"]\n             parsers: %{\"forwarded\" => RemoteIp.Parsers.Forwarded, \"forwarding\" => Parsers.Forwarding}\n             proxies: []\n             clients: []\n           [debug] Parsed IPs from forwarding headers: [{1, 2, 3, 4}, {2, 3, 4, 5}]\n           \"\"\"\n  end\nend\n"
  },
  {
    "path": "integration/tests/parsers/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "integration/tests/purge/.formatter.exs",
    "content": "[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "integration/tests/purge/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\npurge-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n"
  },
  {
    "path": "integration/tests/purge/README.md",
    "content": "# Purge integration test\n\nThis app enables remote\\_ip debugging, just like the [debug](../debug) integration test. However, it also adjusts the `Logger` configuration to make sure that the [`:compile_time_purge_matching` option](https://hexdocs.pm/logger/1.11.3/Logger.html#module-application-configuration) still works when remote\\_ip gets recompiled.\n\nSpecifically, we purge messages with a level lower than `:info`, which includes the `:debug` messages that `RemoteIp.Debugger` generates. This means that when we compile remote\\_ip, none of the debug statements should survive. Even when we set the log level to `:debug` at runtime in the tests, the logs should have been purged at compile-time.\n\nThis is an important regression to test because older versions of remote\\_ip instructed people to disable debug logs using `:compile_time_purge_matching` (cf. [`4512fe5`](https://github.com/ajvondrak/remote_ip/commit/4512fe53cd2b9c2e03924b12961e48a1ff5b0299)), so we should make an effort to ensure their configurations keep working. Of course, it's still possible they used a `:module`/`:function` matcher that is no longer relevant due to the changing internals of the remote\\_ip code. But that's on them for matching against private implementation details. 🙃\n"
  },
  {
    "path": "integration/tests/purge/config/config.exs",
    "content": "import Config\n\nconfig :logger, :console,\n  format: \"[$level] $message\\n\",\n  colors: [enabled: false]\n\nconfig :logger, compile_time_purge_matching: [[level_lower_than: :info]]\n\nconfig :remote_ip, debug: true\n"
  },
  {
    "path": "integration/tests/purge/mix.exs",
    "content": "defmodule Purge.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :purge,\n      version: \"0.0.0\",\n      elixir: \"~> 1.12\",\n      deps: [remote_ip: [path: \"../../..\"]]\n    ]\n  end\n\n  def application do\n    [extra_applications: [:logger]]\n  end\nend\n"
  },
  {
    "path": "integration/tests/purge/test/purge_test.exs",
    "content": "defmodule PurgeTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  @head [{\"x-forwarded-for\", \"3.14.15.9\"}]\n\n  @conn %Plug.Conn{\n    remote_ip: {127, 0, 0, 1},\n    req_headers: @head\n  }\n\n  def call(conn, opts \\\\ []) do\n    RemoteIp.call(conn, RemoteIp.init(opts))\n  end\n\n  def from(head, opts \\\\ []) do\n    RemoteIp.from(head, opts)\n  end\n\n  test \"logs get purged at compile time\" do\n    Logger.configure(level: :debug)\n    assert capture_log(fn -> call(@conn) end) == \"\"\n    assert capture_log(fn -> from(@head) end) == \"\"\n  end\nend\n"
  },
  {
    "path": "integration/tests/purge/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "integration/tests.exs",
    "content": "defmodule Integration.Tests do\n  @path Path.join(__DIR__, \"tests\")\n\n  if IO.ANSI.enabled?() do\n    @color \"--color\"\n  else\n    @color \"--no-color\"\n  end\n\n  def run do\n    File.ls!(@path) |> Enum.map(&run/1) |> summarize()\n  end\n\n  def run(app) do\n    IO.puts(\"---> Running integration tests on #{app} app\")\n\n    with 0 <- mix(app, \"deps.clean\", [\"--build\", \"remote_ip\"]),\n         0 <- mix(app, \"deps.get\"),\n         0 <- mix(app, \"test\", [@color]) do\n      {app, :pass}\n    else\n      _ -> {app, :fail}\n    end\n  end\n\n  def mix(app, task, args \\\\ []) do\n    cmd = [task | args]\n    dir = Path.expand(app, @path)\n    out = IO.binstream(:stdio, :line)\n\n    IO.puts([\"-->\", \"mix\" | cmd] |> Enum.join(\" \"))\n    {_, status} = System.cmd(\"mix\", cmd, cd: dir, into: out)\n    status\n  end\n\n  def summarize(results) do\n    count(results)\n\n    Enum.each(results, fn\n      {app, :pass} -> passed(app)\n      {app, :fail} -> failed(app)\n    end)\n  end\n\n  def count(results) do\n    tests = length(results)\n    fails = Enum.count(results, fn {_, flag} -> flag == :fail end)\n    msg = [plural(tests, \"integration test\"), \", \", plural(fails, \"failure\")]\n\n    IO.puts(\"\")\n\n    if fails > 0 do\n      IO.ANSI.format([:red | msg]) |> IO.puts()\n    else\n      IO.ANSI.format([:green | msg]) |> IO.puts()\n    end\n  end\n\n  def plural(1, string), do: \"1 #{string}\"\n  def plural(n, string), do: \"#{n} #{string}s\"\n\n  def passed(app) do\n    IO.ANSI.format([:green, \"  ✓ #{app}\"]) |> IO.puts()\n  end\n\n  def failed(app) do\n    IO.ANSI.format([:red, \"  ✗ #{app}\"]) |> IO.puts()\n    System.at_exit(fn _ -> exit({:shutdown, 1}) end)\n  end\nend\n\nIntegration.Tests.run()\n"
  },
  {
    "path": "lib/remote_ip/block.ex",
    "content": "defmodule RemoteIp.Block do\n  import Bitwise\n  alias __MODULE__\n\n  @moduledoc false\n\n  defstruct [:proto, :net, :mask]\n\n  def encode({a, b, c, d}) do\n    <<ip::32>> = <<a::8, b::8, c::8, d::8>>\n    {:v4, ip}\n  end\n\n  def encode({a, b, c, d, e, f, g, h}) do\n    <<ip::128>> = <<a::16, b::16, c::16, d::16, e::16, f::16, g::16, h::16>>\n    {:v6, ip}\n  end\n\n  def contains?(%Block{proto: proto, net: net, mask: mask}, {proto, ip}) do\n    (ip &&& mask) == net\n  end\n\n  def contains?(%Block{}, {_, _}) do\n    false\n  end\n\n  def parse!(cidr) do\n    case parse(cidr) do\n      {:ok, block} -> block\n      {:error, message} -> raise ArgumentError, message\n    end\n  end\n\n  def parse(cidr) do\n    case process(:parts, String.split(cidr, \"/\", parts: 2)) do\n      {:error, e} -> {:error, \"#{e} in CIDR #{inspect(cidr)}\"}\n      ok -> ok\n    end\n  end\n\n  defp process(:parts, [ip, prefix]) do\n    with {:ok, ip} <- process(:ip, ip),\n         {:ok, prefix} <- process(:prefix, prefix) do\n      process(:block, ip, prefix)\n    end\n  end\n\n  defp process(:parts, [ip]) do\n    with {:ok, ip} <- process(:ip, ip) do\n      process(:block, ip)\n    end\n  end\n\n  defp process(:ip, address) do\n    case :inet.parse_strict_address(address |> to_charlist()) do\n      {:ok, ip} -> {:ok, encode(ip)}\n      {:error, _} -> {:error, \"Invalid address #{inspect(address)}\"}\n    end\n  end\n\n  defp process(:prefix, prefix) do\n    try do\n      {:ok, String.to_integer(prefix)}\n    rescue\n      ArgumentError -> {:error, \"Invalid prefix #{inspect(prefix)}\"}\n    end\n  end\n\n  defp process(:block, {:v4, ip}) do\n    process(:block, {:v4, ip}, 32)\n  end\n\n  defp process(:block, {:v6, ip}) do\n    process(:block, {:v6, ip}, 128)\n  end\n\n  defp process(:block, {:v4, ip}, prefix) when prefix in 0..32 do\n    ones = 0xFFFFFFFF\n    <<mask::32>> = <<bnot(ones >>> prefix)::32>>\n    {:ok, %Block{proto: :v4, net: ip &&& mask, mask: mask}}\n  end\n\n  defp process(:block, {:v6, ip}, prefix) when prefix in 0..128 do\n    ones = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n    <<mask::128>> = <<bnot(ones >>> prefix)::128>>\n    {:ok, %Block{proto: :v6, net: ip &&& mask, mask: mask}}\n  end\n\n  defp process(:block, _, prefix) do\n    {:error, \"Invalid prefix #{inspect(prefix)}\"}\n  end\n\n  defimpl String.Chars, for: Block do\n    def to_string(%Block{proto: :v4, net: net, mask: mask}) do\n      <<a::8, b::8, c::8, d::8>> = <<net::32>>\n      \"#{:inet.ntoa({a, b, c, d})}/#{bits(mask)}\"\n    end\n\n    def to_string(%Block{proto: :v6, net: net, mask: mask}) do\n      <<a::16, b::16, c::16, d::16, e::16, f::16, g::16, h::16>> = <<net::128>>\n      \"#{:inet.ntoa({a, b, c, d, e, f, g, h})}/#{bits(mask)}\"\n    end\n\n    defp bits(mask) do\n      ones = for <<1::1 <- :binary.encode_unsigned(mask)>>, do: 1\n      length(ones)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/remote_ip/debugger.ex",
    "content": "defmodule RemoteIp.Debugger do\n  require Logger\n\n  @moduledoc \"\"\"\n  Compile-time debugging facilities.\n\n  `RemoteIp` uses the `debug/3` macro to instrument its implementation with\n  *debug events* at compile time. If an event is enabled, the macro will expand\n  into a `Logger.debug/2` call with a specific message. If an event is\n  disabled, the logging will be purged, thus generating no extra code and\n  having no impact on run time.\n\n  ## Basic usage\n\n  Events are fired on every call to `RemoteIp.call/2` or `RemoteIp.from/2`. To\n  enable or disable all debug events at once, you can set a boolean in your\n  `Config` file:\n\n  ```elixir\n  config :remote_ip, debug: true\n  ```\n\n  By default, the debugger is turned off (i.e., `debug: false`).\n\n  Because `RemoteIp.Debugger` works at compile time, you must make sure to\n  recompile the `:remote_ip` dependency whenever you change the configuration:\n\n  ```console\n  $ mix deps.clean --build remote_ip\n  ```\n\n  ## Advanced usage\n\n  You may also pass a list of atoms into the `:debug` configuration naming\n  which events to log.\n\n  These are all the possible events:\n\n  * `:options` - the keyword options *after* any runtime configuration has been\n    evaluated (see `RemoteIp.Options`)\n\n  * `:headers` - all incoming headers, either from the `Plug.Conn`'s\n    `req_headers` or the list passed directly into `RemoteIp.from/2`; useful\n    for seeing if you're even getting the forwarding headers you expect in the\n    first place\n\n  * `:forwarding` - the subset of headers (as configured by `RemoteIp.Options`)\n    that contain forwarding information\n\n  * `:ips` - the entire sequence of IP addresses parsed from the forwarding\n    headers, in order\n\n  * `:type` - for each IP (until we find the client), classifies the address\n    either as a known client, a known proxy, a reserved address, or none of the\n    above (and thus presumably a client)\n\n  * `:ip` - the final result of the remote IP processing; when rewriting the\n    `Plug.Conn`'s `remote_ip`, the message will tell you the original IP that\n    is being replaced\n\n  Therefore, `debug: true` is equivalent to passing in all of the above:\n\n  ```elixir\n  config :remote_ip, debug: [:options, :headers, :forwarding, :ips, :type, :ip]\n  ```\n\n  But you could disable certain events by removing them from the list. For\n  example, to log only the incoming headers and resulting IP:\n\n  ```elixir\n  config :remote_ip, debug: [:headers, :ip]\n  ```\n\n  ## Interactions with `Logger`\n\n  Since they both work at compile time, your configuration of `:logger` will\n  also affect the operation of `RemoteIp.Debugger`. For example, it's possible\n  to enable debugging but still purge all the resulting logs:\n\n  ```elixir\n  # All events *would* be logged...\n  config :remote_ip, debug: true\n\n  # ...But :debug logs will actually get purged at compile time\n  config :logger, compile_time_purge_matching: [[level_lower_than: :info]]\n  ```\n  \"\"\"\n\n  @doc \"\"\"\n  An internal macro for generating debug logs.\n\n  There is no reason for you to call this directly. It's used to instrument the\n  `RemoteIp` module at compilation time.\n  \"\"\"\n\n  @spec debug(atom(), [any()], do: any()) :: any()\n\n  defmacro debug(id, inputs \\\\ [], do: output) do\n    if debug?(id) do\n      quote do\n        inputs = unquote(inputs)\n        output = unquote(output)\n        unquote(__MODULE__).__log__(unquote(id), inputs, output)\n        output\n      end\n    else\n      output\n    end\n  end\n\n  @debug Application.compile_env(:remote_ip, :debug, false)\n\n  cond do\n    is_list(@debug) ->\n      defp debug?(id), do: Enum.member?(@debug, id)\n\n    is_boolean(@debug) ->\n      defp debug?(_), do: @debug\n  end\n\n  def __log__(id, inputs, output) do\n    Logger.debug(__message__(id, inputs, output))\n  end\n\n  def __message__(:options, [], options) do\n    headers = inspect(options[:headers])\n    parsers = inspect(options[:parsers])\n    proxies = inspect(options[:proxies] |> Enum.map(&to_string/1))\n    clients = inspect(options[:clients] |> Enum.map(&to_string/1))\n\n    [\n      \"Processing remote IP\\n\",\n      \"  headers: #{headers}\\n\",\n      \"  parsers: #{parsers}\\n\",\n      \"  proxies: #{proxies}\\n\",\n      \"  clients: #{clients}\"\n    ]\n  end\n\n  def __message__(:headers, [], headers) do\n    \"Taking forwarding headers from #{inspect(headers)}\"\n  end\n\n  def __message__(:forwarding, [], headers) do\n    \"Parsing IPs from forwarding headers: #{inspect(headers)}\"\n  end\n\n  def __message__(:ips, [], ips) do\n    \"Parsed IPs from forwarding headers: #{inspect(ips)}\"\n  end\n\n  def __message__(:type, [ip], type) do\n    case type do\n      :client -> \"#{inspect(ip)} is a known client IP\"\n      :proxy -> \"#{inspect(ip)} is a known proxy IP\"\n      :reserved -> \"#{inspect(ip)} is a reserved IP\"\n      :unknown -> \"#{inspect(ip)} is an unknown IP, assuming it's the client\"\n    end\n  end\n\n  def __message__(:ip, [old_conn], new_conn) do\n    origin = inspect(old_conn.remote_ip)\n    client = inspect(new_conn.remote_ip)\n\n    if client != origin do\n      \"Processed remote IP, found client #{client} to replace #{origin}\"\n    else\n      \"Processed remote IP, no client found to replace #{origin}\"\n    end\n  end\n\n  def __message__(:ip, [], ip) do\n    if ip == nil do\n      \"Processed remote IP, no client found\"\n    else\n      \"Processed remote IP, found client #{inspect(ip)}\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/remote_ip/headers.ex",
    "content": "defmodule RemoteIp.Headers do\n  @moduledoc \"\"\"\n  Functions for parsing IPs from multiple types of forwarding headers.\n  \"\"\"\n\n  @doc \"\"\"\n  Extracts all headers with the given names.\n\n  Note that `Plug.Conn` headers are assumed to have been normalized to\n  lowercase, so the names you give should be in lowercase as well.\n\n  ## Examples\n\n      iex> [{\"x-foo\", \"foo\"}, {\"x-bar\", \"bar\"}, {\"x-baz\", \"baz\"}]\n      ...> |> RemoteIp.Headers.take([\"x-foo\", \"x-baz\", \"x-qux\"])\n      [{\"x-foo\", \"foo\"}, {\"x-baz\", \"baz\"}]\n\n      iex> [{\"x-dup\", \"foo\"}, {\"x-dup\", \"bar\"}, {\"x-dup\", \"baz\"}]\n      ...> |> RemoteIp.Headers.take([\"x-dup\"])\n      [{\"x-dup\", \"foo\"}, {\"x-dup\", \"bar\"}, {\"x-dup\", \"baz\"}]\n  \"\"\"\n\n  @spec take(Plug.Conn.headers(), [binary()]) :: Plug.Conn.headers()\n\n  def take(headers, names) do\n    Enum.filter(headers, fn {name, _} -> name in names end)\n  end\n\n  @doc \"\"\"\n  Parses IP addresses out of the given headers.\n\n  For each header name/value pair, the value is parsed for zero or more IP\n  addresses by the parser corresponding to the name. If no such parser exists\n  in the given map, we fall back to `RemoteIp.Parsers.Generic`.\n\n  The IPs are concatenated together into a single flat list. Note that the\n  relative order is preserved. That is, each header produce multiple IPs that\n  are kept in the order given by that specific header. Then, in the case of\n  multiple headers, the concatenated list maintains the same order as the\n  headers appeared in the original name/value list.\n\n  Due to the error-safe nature of the `RemoteIp.Parser` behaviour, headers that\n  do not actually contain valid IP addresses should be safely ignored.\n\n  ## Examples\n\n      iex> [{\"x-one\", \"1.2.3.4, 2.3.4.5\"}, {\"x-two\", \"3.4.5.6, 4.5.6.7\"}]\n      ...> |> RemoteIp.Headers.parse()\n      [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}]\n\n      iex> [{\"forwarded\", \"for=1.2.3.4\"}, {\"x-forwarded-for\", \"2.3.4.5\"}]\n      ...> |> RemoteIp.Headers.parse()\n      [{1, 2, 3, 4}, {2, 3, 4, 5}]\n\n      iex> [{\"accept\", \"*/*\"}, {\"user-agent\", \"ua\"}, {\"x-real-ip\", \"1.2.3.4\"}]\n      ...> |> RemoteIp.Headers.parse()\n      [{1, 2, 3, 4}]\n  \"\"\"\n\n  @spec parse(Plug.Conn.headers(), %{binary() => RemoteIp.Parser.t()}) :: [\n          :inet.ip_address()\n        ]\n\n  def parse(headers, parsers \\\\ RemoteIp.Options.default(:parsers)) do\n    Enum.flat_map(headers, fn {name, value} ->\n      parser = Map.get(parsers, name, RemoteIp.Parsers.Generic)\n      parser.parse(value)\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/remote_ip/options.ex",
    "content": "defmodule RemoteIp.Options do\n  @headers ~w[forwarded x-forwarded-for x-client-ip x-real-ip]\n  @parsers %{\"forwarded\" => RemoteIp.Parsers.Forwarded}\n  @proxies []\n  @clients []\n\n  @moduledoc \"\"\"\n  The keyword options given to `RemoteIp.init/1` or `RemoteIp.from/2`.\n\n  You shouldn't need to use this module directly. Its functions are used\n  internally by `RemoteIp` to process configurations and support MFA-style\n  [runtime options](#module-runtime-options).\n\n  You may pass any of the following keyword arguments into the plug (they get\n  passed to `RemoteIp.init/1`). You can also pass the same keywords directly to\n  `RemoteIp.from/2`.\n\n  ## `:headers`\n\n  The `:headers` option should be a list of strings. These are the names of\n  headers that contain forwarding information. The default is\n\n  ```elixir\n  #{inspect(@headers, pretty: true)}\n  ```\n\n  Every request header whose name exactly matches one of these strings will be\n  parsed for IP addresses, which are then used to determine the routing\n  information and ultimately the original client IP. Note that `Plug`\n  normalizes headers to lowercase, so this option should consist of lowercase\n  names.\n\n  In production, you likely want this to be a singleton - a list of only one\n  string. There are a couple reasons:\n\n  1. You usually can't rely on servers to preserve the relative ordering of\n     headers in the HTTP request. For example, the\n     [Cowboy](https://github.com/ninenines/cowboy/) server presently [uses\n     maps](https://github.com/elixir-plug/plug_cowboy/blob/f82f2ff982f04fb4faa3a12fd2b08a7cc56ebe15/lib/plug/cowboy/conn.ex#L125-L127)\n     to represent headers, which don't preserve key order. The order in which\n     we process IPs matters because we take that as the routing information for\n     the request. So if you have multiple competing headers, the routing might\n     be ambiguous, and you could get bad results.\n\n  2. It could also be a security issue. Say you're only expecting one header\n     like `X-Forwarded-For`, but configure multiple headers like\n     `[\"x-forwarded-for\", \"x-real-ip\"]`. Then it'd be easy for a malicious user\n     to just set an extra `X-Real-Ip` header and interfere with the IP parsing\n     (again, due to the sensitive nature of header ordering).\n\n  We still allow multiple headers because:\n\n  1. Users can get up & running faster if the default configuration recognizes\n     all of the common headers.\n\n  2. You shouldn't be relying that heavily on IP addresses for security. Even a\n     single plain-text header has enough problems on its own that we can't\n     guarantee its results are accurate. For more details, see the\n     documentation for [the algorithm](algorithm.md).\n\n  3. It's more general. Networking setups are often very idiosyncratic, and we\n     want to give users the option to use multiple headers if that's what they\n     need.\n\n  ## `:parsers`\n\n  The `:parsers` option should be a map from strings to modules. Each string\n  should be a header name (lowercase), and each module should implement the\n  `RemoteIp.Parser` behaviour. The default is\n\n\n  ```elixir\n  #{inspect(@parsers, pretty: true)}\n  ```\n\n  Headers with the given name are parsed using the given module. If a header is\n  not found in this map, it will be parsed by `RemoteIp.Parsers.Generic`. So\n  you can use this option to:\n\n  * add a parser for your own custom header\n\n  * specialize on the generic parsing of headers like `\"x-forwarded-for\"`\n\n  * replace any of the default parsers with one of your own\n\n  The map you provide for this option is automatically merged into the default\n  using `Map.merge/2`. That way, the stock parsers won't be overridden unless\n  you explicitly provide your own replacement.\n\n  ## `:proxies`\n\n  The `:proxies` option should be a list of strings - either individual IPs or\n  ranges in\n  [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)\n  notation. The default is\n\n\n  ```elixir\n  #{inspect(@proxies, pretty: true)}\n  ```\n\n  For the sake of efficiency, you should prefer CIDR notation where possible.\n  So instead of listing out 256 different addresses for the `1.2.3.x` block,\n  you should say `\"1.2.3.0/24\"`.\n\n  These proxies are skipped by [the algorithm](algorithm.md) and are never\n  considered the original client IP, unless specifically overruled by the\n  `:clients` option.\n\n  In addition to the proxies listed here, note that the following [reserved IP\n  addresses](https://en.wikipedia.org/wiki/Reserved_IP_addresses) are also\n  skipped automatically, as they are presumed to be internal addresses that\n  don't belong to the client:\n\n  * IPv4 loopback: `127.0.0.0/8`\n  * IPv6 loopback: `::1/128`\n  * IPv4 private network: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n  * IPv6 unique local address: `fc00::/7`\n\n  ## `:clients`\n\n  The `:clients` option should be a list of strings - either individual IPs or\n  ranges in\n  [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)\n  notation. The default is\n\n\n  ```elixir\n  #{inspect(@clients, pretty: true)}\n  ```\n\n  For the sake of efficiency, you should prefer CIDR notation where possible.\n  So instead of listing out 256 different addresses for the `1.2.3.x` block,\n  you should say `\"1.2.3.0/24\"`.\n\n  These addresses are never considered to be proxies by [the\n  algorithm](algorithm.md). For example, if you configure the `:proxies` option\n  to include `\"1.2.3.0/24\"` and the `:clients` option to include `\"1.2.3.4\"`,\n  then every IP in the `1.2.3.x` block would be considered a proxy *except* for\n  `1.2.3.4`.\n\n  This option can also be used on reserved IP addresses that would otherwise be\n  skipped automatically. For example, if your routing works through a local\n  network, you might actually consider addresses in the `10.x.x.x` block to be\n  clients. You could permit the entire block with `\"10.0.0.0/8\"`, or even\n  specific IPs in this range like `\"10.1.2.3\"`.\n\n  ## Runtime options\n\n  Every option can also accept a tuple of three elements: `{module, function,\n  arguments}` (MFA). These are passed to `Kernel.apply/3` at runtime, allowing\n  you to dynamically configure the plug, even though the `Plug.Builder`\n  generally calls `c:Plug.init/1` at compilation time.\n\n  The return value from an MFA should be the same as if you were passing the\n  literal into that option. For instance, the `:proxies` MFA should return a\n  list of IP/CIDR strings.\n\n  The MFAs you give are re-evaluated on *each call* to `RemoteIp.call/2` or\n  `RemoteIp.from/2`. So be careful not to do anything too expensive at runtime.\n  For example, don't download a list of known proxies, or else it will be\n  re-downloaded on every request. Consider caching the download instead,\n  perhaps using a library like [`Cachex`](https://hexdocs.pm/cachex).\n\n  ## Examples\n\n  ### Basic usage\n\n  Suppose you know:\n  * you are behind proxies in the `1.2.x.x` block\n  * the proxies use the `X-Real-Ip` header\n  * but the IP `1.2.3.4` is actually a client, not one of the proxies\n\n  Then you could say:\n\n  ```elixir\n  defmodule MyApp do\n    use Plug.Router\n\n    plug RemoteIp,\n      headers: ~w[x-real-ip],\n      proxies: ~w[1.2.0.0/16],\n      clients: ~w[1.2.3.4]\n\n    plug :match\n    plug :dispatch\n\n    # get \"/\" do ...\n  end\n  ```\n\n  The same options may also be passed into `RemoteIp.from/2`:\n\n  ```elixir\n  defmodule MySocket do\n    use Phoenix.Socket\n\n    @options [\n      headers: ~w[x-real-ip],\n      proxies: ~w[1.2.0.0/16],\n      clients: ~w[1.2.3.4]\n    ]\n\n    def connect(params, socket, connect_info) do\n      ip = RemoteIp.from(connect_info[:x_headers], @options)\n      # ...\n    end\n  end\n  ```\n\n  ### Custom parser\n\n  Suppose your proxies are using a header with a special format. The name of\n  the header is `X-Special` and the format looks like `ip=127.0.0.1`.\n\n  First, you'd implement a custom parser:\n\n  ```elixir\n  defmodule SpecialParser do\n    @behaviour RemoteIp.Parser\n\n    @impl RemoteIp.Parser\n    def parse(header) do\n      ip = String.replace_prefix(header, \"ip=\", \"\")\n      case :inet.parse_strict_address(ip |> to_charlist()) do\n        {:ok, parsed} -> [parsed]\n        _ -> []\n      end\n    end\n  end\n  ```\n\n  Then you would configure the plug with that parser. Make sure to also specify\n  the `:headers` option so that the `X-Special` header actually gets passed to\n  the parser.\n\n  ```elixir\n  defmodule SpecialApp do\n    use Plug.Router\n\n    plug RemoteIp,\n      headers: ~w[x-special],\n      parsers: %{\"x-special\" => SpecialParser}\n\n    plug :match\n    plug :dispatch\n\n    # get \"/\" do ...\n  end\n  ```\n\n  ### Using MFAs\n\n  Suppose you're deploying a release and you want to get the proxy IPs from an\n  environment variable. Because the release is compiled ahead of time, you\n  shouldn't do a `System.get_env/1` inline - it'll just be the value of the\n  environment variable circa compilation time (probably empty!).\n\n  ```elixir\n  defmodule CompiledApp do\n    use Plug.Router\n\n    # DON'T DO THIS: the value of the env var gets compiled into the release\n    plug RemoteIp, proxies: System.get_env(\"PROXIES\") |> String.split(\",\")\n\n    plug :match\n    plug :dispatch\n\n    # get \"/\" do ...\n  end\n  ```\n\n  Instead, you can use an MFA to look up the variable at runtime:\n\n  ```elixir\n  defmodule RuntimeApp do\n    use Plug.Router\n\n    plug RemoteIp, proxies: {__MODULE__, :proxies, []}\n\n    def proxies do\n      System.get_env(\"PROXIES\") |> String.split(\",\", trim: true)\n    end\n\n    plug :match\n    plug :dispatch\n\n    # get \"/\" do ...\n  end\n  ```\n  \"\"\"\n\n  @doc \"\"\"\n  The default value for the given option.\n  \"\"\"\n\n  def default(option)\n  def default(:headers), do: @headers\n  def default(:parsers), do: @parsers\n  def default(:proxies), do: @proxies\n  def default(:clients), do: @clients\n\n  @doc \"\"\"\n  Processes keyword options, delaying the evaluation of MFAs until `unpack/1`.\n  \"\"\"\n\n  def pack(options) do\n    [\n      headers: pack(options, :headers),\n      parsers: pack(options, :parsers),\n      proxies: pack(options, :proxies),\n      clients: pack(options, :clients)\n    ]\n  end\n\n  defp pack(options, option) do\n    case Keyword.get(options, option, default(option)) do\n      {m, f, a} -> {m, f, a}\n      value -> evaluate(option, value)\n    end\n  end\n\n  @doc \"\"\"\n  Evaluates options processed by `pack/1`, applying MFAs as needed.\n  \"\"\"\n\n  def unpack(options) do\n    [\n      headers: unpack(options, :headers),\n      parsers: unpack(options, :parsers),\n      proxies: unpack(options, :proxies),\n      clients: unpack(options, :clients)\n    ]\n  end\n\n  defp unpack(options, option) do\n    case Keyword.get(options, option) do\n      {m, f, a} -> evaluate(option, apply(m, f, a))\n      value -> value\n    end\n  end\n\n  defp evaluate(:headers, headers) do\n    headers\n  end\n\n  defp evaluate(:parsers, parsers) do\n    Map.merge(default(:parsers), parsers)\n  end\n\n  defp evaluate(:proxies, proxies) do\n    proxies |> Enum.map(&RemoteIp.Block.parse!/1)\n  end\n\n  defp evaluate(:clients, clients) do\n    clients |> Enum.map(&RemoteIp.Block.parse!/1)\n  end\nend\n"
  },
  {
    "path": "lib/remote_ip/parser.ex",
    "content": "defmodule RemoteIp.Parser do\n  @moduledoc \"\"\"\n  Defines the interface for parsing headers into IP addresses.\n\n  `RemoteIp.Headers.parse/1` dynamically dispatches to different parser modules\n  depending on the name of the header. For example, the `\"forwarded\"` header is\n  parsed by `RemoteIp.Parsers.Forwarded`, which implements this behaviour.\n  \"\"\"\n\n  @typedoc \"\"\"\n  Any module that implements the `RemoteIp.Parser` behaviour.\n  \"\"\"\n\n  @type t() :: module()\n\n  @doc \"\"\"\n  Parses the specific header's value into a list of IP addresses.\n\n  This callback should be error-safe. For instance, if the header's value is\n  invalid, it should return an empty list.\n\n  The actual work of converting an individual IP address string into the tuple\n  type should typically be done using `:inet` functions such as\n  `:inet.parse_strict_address/1`.\n\n  Note that a header may also contain more than one IP address. The order of\n  the list is important because it's interpreted as routing information.\n  Conceptually, the leftmost IP is the source of the request (the client), the\n  rightmost IP is the destination (your server), and anything in the middle\n  lists the proxy hops in order. However, in reality, there may be bad actors\n  or strange routing that makes this more complicated. It's the job of\n  `RemoteIp` to sort that out. This callback should *only* be concerned with\n  faithfully parsing the literal order given by the header.\n  \"\"\"\n\n  @callback parse(header :: binary()) :: [:inet.ip_address()]\nend\n"
  },
  {
    "path": "lib/remote_ip/parsers/forwarded.ex",
    "content": "defmodule RemoteIp.Parsers.Forwarded do\n  use Combine\n\n  @behaviour RemoteIp.Parser\n\n  @moduledoc \"\"\"\n  [RFC 7239](https://tools.ietf.org/html/rfc7239) compliant parser for\n  `Forwarded` headers.\n\n  This module implements the `RemoteIp.Parser` behaviour. IPs are parsed out of\n  the `for=` pairs across each forwarded element.\n\n  ## Examples\n\n      iex> RemoteIp.Parsers.Forwarded.parse(\"for=1.2.3.4;by=2.3.4.5\")\n      [{1, 2, 3, 4}]\n\n      iex> RemoteIp.Parsers.Forwarded.parse(\"for=\\\\\"[::1]\\\\\", for=\\\\\"[::2]\\\\\"\")\n      [{0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 2}]\n\n      iex> RemoteIp.Parsers.Forwarded.parse(\"invalid\")\n      []\n  \"\"\"\n\n  @impl RemoteIp.Parser\n\n  def parse(header) do\n    case Combine.parse(header, forwarded()) do\n      [elements] -> Enum.flat_map(elements, &parse_forwarded_for/1)\n      _ -> []\n    end\n  end\n\n  defp parse_forwarded_for(pairs) do\n    case fors_from(pairs) do\n      [string] -> parse_ip(string)\n      _ambiguous -> []\n    end\n  end\n\n  defp fors_from(pairs) do\n    for {key, val} <- pairs, String.downcase(key) == \"for\", do: val\n  end\n\n  defp parse_ip(string) do\n    case Combine.parse(string, ip_address()) do\n      [ip] -> [ip]\n      _ -> []\n    end\n  end\n\n  # https://tools.ietf.org/html/rfc7239#section-4\n\n  defp forwarded do\n    sep_by(forwarded_element(), comma()) |> eof()\n  end\n\n  defp forwarded_element do\n    sep_by1(forwarded_pair(), char(\";\"))\n  end\n\n  defp forwarded_pair do\n    pair = [token(), ignore(char(\"=\")), value()]\n    pipe(pair, &List.to_tuple/1)\n  end\n\n  defp value do\n    either(token(), quoted_string())\n  end\n\n  # https://tools.ietf.org/html/rfc7230#section-3.2.6\n\n  defp token do\n    word_of(~r/[!#$%&'*+\\-.^_`|~0-9a-zA-Z]/)\n  end\n\n  defp quoted_string do\n    quoted(string_of(either(qdtext(), quoted_pair())))\n  end\n\n  defp quoted(parser) do\n    between(char(\"\\\"\"), parser, char(\"\\\"\"))\n  end\n\n  defp string_of(parser) do\n    map(many(parser), &Enum.join/1)\n  end\n\n  defp qdtext do\n    word_of(~r/[\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]/)\n  end\n\n  @quotable ([?\\t] ++ Enum.to_list(0x21..0x7E) ++ Enum.to_list(0x80..0xFF))\n            |> Enum.map(&<<&1::utf8>>)\n\n  defp quoted_pair do\n    ignore(char(\"\\\\\")) |> one_of(char(), @quotable)\n  end\n\n  # https://tools.ietf.org/html/rfc7230#section-7\n\n  defp comma do\n    skip(many(either(space(), tab())))\n    |> char(\",\")\n    |> skip(many(either(space(), tab())))\n  end\n\n  # https://tools.ietf.org/html/rfc7239#section-6\n\n  defp ip_address do\n    node_name()\n    |> ignore(option(ignore(char(\":\")) |> node_port()))\n    |> eof()\n  end\n\n  defp node_name do\n    choice([\n      ipv4_address(),\n      between(char(\"[\"), ipv6_address(), char(\"]\")),\n      ignore(string(\"unknown\")),\n      ignore(obfuscated())\n    ])\n  end\n\n  defp node_port(previous) do\n    previous |> either(port(), obfuscated())\n  end\n\n  defp port do\n    # Have to try to parse the wider integers first due to greediness. For\n    # example, the port \"12345\" would be matched by fixed_integer(1) and the\n    # remaining \"2345\" would cause a parse error for the eof in ip_address/0.\n\n    choice(Enum.map(5..1//-1, &fixed_integer/1))\n  end\n\n  defp obfuscated do\n    word_of(~r/^_[a-zA-Z0-9._\\-]+/)\n  end\n\n  # Could follow the ABNF described in\n  # https://tools.ietf.org/html/rfc3986#section-3.2.2, but prefer to lean on\n  # the existing :inet parser - we want its output anyway.\n\n  defp ipv4_address do\n    map(word_of(~r/[0-9.]/), fn string ->\n      case :inet.parse_ipv4strict_address(string |> to_charlist()) do\n        {:ok, ip} -> ip\n        {:error, :einval} -> {:error, \"Invalid IPv4 address\"}\n      end\n    end)\n  end\n\n  defp ipv6_address do\n    map(word_of(~r/[0-9a-f:.]/i), fn string ->\n      case :inet.parse_ipv6strict_address(string |> to_charlist()) do\n        {:ok, ip} -> ip\n        {:error, :einval} -> {:error, \"Invalid IPv6 address\"}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/remote_ip/parsers/generic.ex",
    "content": "defmodule RemoteIp.Parsers.Generic do\n  @behaviour RemoteIp.Parser\n\n  @moduledoc \"\"\"\n  Generic parser for forwarding headers.\n\n  This module implements the `RemoteIp.Parser` behaviour. When there is not a\n  more specific parser, `RemoteIp.Headers.parse/1` falls back to using this\n  one.\n\n  The value is parsed simply as a comma-separated list of IPs. This is suitable\n  for a wide range of headers, such as `X-Forwarded-For`, `X-Real-IP`, and\n  `X-Client-IP`.\n\n  Any amount of whitespace is allowed before and after the commas, as well as\n  at the beginning & end of the input.\n\n  ## Examples\n\n      iex> RemoteIp.Parsers.Generic.parse(\"1.2.3.4, 5.6.7.8\")\n      [{1, 2, 3, 4}, {5, 6, 7, 8}]\n\n      iex> RemoteIp.Parsers.Generic.parse(\"  ::1  \")\n      [{0, 0, 0, 0, 0, 0, 0, 1}]\n\n      iex> RemoteIp.Parsers.Generic.parse(\"invalid\")\n      []\n  \"\"\"\n\n  @impl RemoteIp.Parser\n\n  def parse(header) do\n    header |> split_commas() |> parse_ips()\n  end\n\n  defp split_commas(header) do\n    header |> String.trim() |> String.split(~r/\\s*,\\s*/)\n  end\n\n  defp parse_ips(strings) do\n    List.foldr(strings, [], fn string, ips ->\n      case parse_ip(string) do\n        {:ok, ip} -> [ip | ips]\n        {:error, _} -> ips\n      end\n    end)\n  end\n\n  defp parse_ip(string) do\n    try do\n      :inet.parse_strict_address(string |> to_charlist())\n    rescue\n      UnicodeConversionError -> {:error, :invalid_unicode}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/remote_ip.ex",
    "content": "defmodule RemoteIp do\n  import RemoteIp.Debugger\n\n  @behaviour Plug\n\n  @moduledoc \"\"\"\n  A plug to rewrite the `Plug.Conn`'s `remote_ip` based on forwarding headers.\n\n  Generic comma-separated headers like `X-Forwarded-For`, `X-Real-Ip`, and\n  `X-Client-Ip` are all recognized, as well as the [RFC\n  7239](https://tools.ietf.org/html/rfc7239) `Forwarded` header. IPs are\n  processed last-to-first to prevent IP spoofing. Read more in the\n  documentation for [the algorithm](algorithm.md).\n\n  This plug is highly configurable, giving you the power to adapt it to your\n  particular networking infrastructure:\n\n  * IPs can come from any header(s) you want. You can even implement your own\n    custom parser if you're using a special format.\n\n  * You can configure the IPs of known proxies & clients so that you never get\n    the wrong results.\n\n  * All options are configurable at runtime, so you can deploy a single release\n    but still customize it using environment variables, the `Application`\n    environment, or any other arbitrary mechanism.\n\n  * Still not getting the right IP? You can recompile the plug with debugging\n    enabled to generate logs, and even fine-tune the verbosity by selecting\n    which events to track.\n\n  ## Usage\n\n  This plug should be early in your pipeline, or else the `remote_ip` might not\n  get rewritten before your route's logic executes.\n\n  In [Phoenix](https://hexdocs.pm/phoenix), this might mean plugging `RemoteIp`\n  into your endpoint before the router:\n\n  ```elixir\n  defmodule MyApp.Endpoint do\n    use Phoenix.Endpoint, otp_app: :my_app\n\n    plug RemoteIp\n    # plug ...\n    # plug ...\n    plug MyApp.Router\n  end\n  ```\n\n  But if you only want to rewrite IPs in a narrower part of your app, you could\n  of course put it in an individual pipeline of your router.\n\n  In an ordinary `Plug.Router`, you should make sure `RemoteIp` comes before\n  the `:match`/`:dispatch` plugs:\n\n  ```elixir\n  defmodule MyApp do\n    use Plug.Router\n\n    plug RemoteIp\n    plug :match\n    plug :dispatch\n\n    # get \"/\" do ...\n  end\n  ```\n\n  You can also use `RemoteIp.from/2` to determine an IP from a list of headers.\n  This is useful outside of the plug pipeline, where you may not have access to\n  the `Plug.Conn`. For example, you might only be getting the `x_headers` from\n  [`Phoenix.Socket`](https://hexdocs.pm/phoenix/Phoenix.Socket.html):\n\n  ```elixir\n  defmodule MySocket do\n    use Phoenix.Socket\n\n    def connect(params, socket, connect_info) do\n      ip = RemoteIp.from(connect_info[:x_headers])\n      # ...\n    end\n  end\n  ```\n\n  ## Configuration\n\n  Options may be passed as a keyword list via `RemoteIp.init/1` or directly\n  into `RemoteIp.from/2`. At a high level, the following options are available:\n\n  * `:headers` - a list of header names to consider\n  * `:parsers` - a map from header names to custom parser modules\n  * `:clients` - a list of known client IPs, either plain or in CIDR notation\n  * `:proxies` - a list of known proxy IPs, either plain or in CIDR notation\n\n  You can specify any option using a tuple of `{module, function_name,\n  arguments}`, which will be called dynamically at runtime to get the\n  equivalent value.\n\n  For more details about these options, see `RemoteIp.Options`.\n\n  ## Troubleshooting\n\n  Getting the right configuration can be tricky. Requests might come in with\n  unexpected headers, or maybe you didn't account for certain proxies, or any\n  number of other issues.\n\n  Luckily, you can debug `RemoteIp.call/2` and `RemoteIp.from/2` by updating\n  your `Config` file:\n\n  ```elixir\n  config :remote_ip, debug: true\n  ```\n\n  and recompiling the `:remote_ip` dependency:\n\n  ```console\n  $ mix deps.clean --build remote_ip\n  $ mix deps.compile\n  ```\n\n  Then it will generate log messages showing how the IP gets computed. For more\n  details about these messages, as well advanced usage, see\n  `RemoteIp.Debugger`.\n\n  ## Metadata\n\n  When you use this plug, `RemoteIp.call/2` will populate the `Logger` metadata\n  under the key `:remote_ip`. This will be the string representation of the\n  final value of the `Plug.Conn`'s `remote_ip`. Even if no client was found in\n  the headers, we still set the metadata to the original IP.\n\n  You can use this in your logs by updating your `Config` file:\n\n  ```elixir\n  config :logger,\n    message: \"$metadata[$level] $message\\\\n\",\n    metadata: [:remote_ip]\n  ```\n\n  Then your logs will look something like this:\n\n  ```log\n  [info] Running ExampleWeb.Endpoint with cowboy 2.8.0 at 0.0.0.0:4000 (http)\n  [info] Access ExampleWeb.Endpoint at http://localhost:4000\n  remote_ip=1.2.3.4 [info] GET /\n  remote_ip=1.2.3.4 [debug] Processing with ExampleWeb.PageController.index/2\n    Parameters: %{}\n    Pipelines: [:browser]\n  remote_ip=1.2.3.4 [info] Sent 200 in 21ms\n  ```\n\n  Note that metadata will *not* be set by `RemoteIp.from/2`.\n  \"\"\"\n\n  @impl Plug\n\n  @doc \"\"\"\n  The `c:Plug.init/1` callback.\n\n  This accepts the keyword options described by `RemoteIp.Options`. Because\n  plug initialization typically happens at compile time, we make sure not to\n  evaluate runtime options until `call/2`.\n  \"\"\"\n\n  def init(opts) do\n    RemoteIp.Options.pack(opts)\n  end\n\n  @impl Plug\n\n  @doc \"\"\"\n  The `c:Plug.call/2` callback.\n\n  Rewrites the `Plug.Conn`'s `remote_ip` based on its forwarding headers. Each\n  call will re-evaluate all runtime options. See `RemoteIp.Options` for\n  details.\n  \"\"\"\n\n  def call(conn, opts) do\n    debug :ip, [conn] do\n      ip = ip_from(conn.req_headers, opts) || conn.remote_ip\n      add_metadata(ip)\n      %{conn | remote_ip: ip}\n    end\n  end\n\n  @doc \"\"\"\n  Extracts the remote IP from a list of headers.\n\n  In cases where you don't have access to a full `Plug.Conn` struct, you can\n  use this function to process the remote IP from a list of key-value pairs\n  representing the headers.\n\n  You may specify the same options as if you were using the plug. Runtime\n  options are evaluated each time you call this function. See\n  `RemoteIp.Options` for details.\n\n  If no client IP can be found in the given headers, this function will return\n  `nil`.\n\n  ## Examples\n\n      iex> RemoteIp.from([{\"x-forwarded-for\", \"1.2.3.4\"}])\n      {1, 2, 3, 4}\n\n      iex> [{\"x-foo\", \"1.2.3.4\"}, {\"x-bar\", \"2.3.4.5\"}]\n      ...> |> RemoteIp.from(headers: ~w[x-foo])\n      {1, 2, 3, 4}\n\n      iex> [{\"x-foo\", \"1.2.3.4\"}, {\"x-bar\", \"2.3.4.5\"}]\n      ...> |> RemoteIp.from(headers: ~w[x-bar])\n      {2, 3, 4, 5}\n\n      iex> [{\"x-foo\", \"1.2.3.4\"}, {\"x-bar\", \"2.3.4.5\"}]\n      ...> |> RemoteIp.from(headers: ~w[x-baz])\n      nil\n  \"\"\"\n\n  @spec from(Plug.Conn.headers(), keyword()) :: :inet.ip_address() | nil\n\n  def from(headers, opts \\\\ []) do\n    debug :ip do\n      ip_from(headers, init(opts))\n    end\n  end\n\n  defp ip_from(headers, opts) do\n    opts = options_from(opts)\n    client_from(ips_from(headers, opts), opts)\n  end\n\n  defp options_from(opts) do\n    debug :options do\n      RemoteIp.Options.unpack(opts)\n    end\n  end\n\n  defp ips_from(headers, opts) do\n    debug :ips do\n      headers = forwarding_from(headers, opts)\n      RemoteIp.Headers.parse(headers, opts[:parsers])\n    end\n  end\n\n  defp forwarding_from(headers, opts) do\n    debug :forwarding do\n      debug(:headers, do: headers) |> RemoteIp.Headers.take(opts[:headers])\n    end\n  end\n\n  defp client_from(ips, opts) do\n    Enum.reverse(ips) |> Enum.find(&client?(&1, opts))\n  end\n\n  defp client?(ip, opts) do\n    type(ip, opts) in [:client, :unknown]\n  end\n\n  # https://en.wikipedia.org/wiki/Loopback\n  # https://en.wikipedia.org/wiki/Private_network\n  # https://en.wikipedia.org/wiki/Reserved_IP_addresses\n  @reserved ~w[\n    127.0.0.0/8\n    ::1/128\n    fc00::/7\n    10.0.0.0/8\n    172.16.0.0/12\n    192.168.0.0/16\n  ] |> Enum.map(&RemoteIp.Block.parse!/1)\n\n  defp type(ip, opts) do\n    debug :type, [ip] do\n      ip = RemoteIp.Block.encode(ip)\n\n      cond do\n        opts[:clients] |> contains?(ip) -> :client\n        opts[:proxies] |> contains?(ip) -> :proxy\n        @reserved |> contains?(ip) -> :reserved\n        true -> :unknown\n      end\n    end\n  end\n\n  defp contains?(blocks, ip) do\n    Enum.any?(blocks, &RemoteIp.Block.contains?(&1, ip))\n  end\n\n  defp add_metadata(remote_ip) do\n    case :inet.ntoa(remote_ip) do\n      {:error, _} -> :ok\n      ip -> Logger.metadata(remote_ip: to_string(ip))\n    end\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule RemoteIp.Mixfile do\n  use Mix.Project\n\n  def project do\n    [\n      app: :remote_ip,\n      version: \"1.2.0\",\n      elixir: \"~> 1.12\",\n      description: description(),\n      package: package(),\n      deps: deps(),\n      aliases: aliases(),\n      dialyzer: dialyzer(),\n      docs: docs(),\n      test_coverage: test_coverage()\n    ]\n  end\n\n  def application do\n    [extra_applications: [:logger]]\n  end\n\n  defp description do\n    \"A plug to rewrite the Plug.Conn's remote_ip based on request headers\" <>\n      \" such as Forwarded, X-Forwarded-For, X-Client-Ip, and X-Real-Ip\"\n  end\n\n  defp package do\n    %{\n      files: ~w[lib mix.exs README.md LICENSE],\n      licenses: [\"MIT\"],\n      links: %{\"GitHub\" => \"https://github.com/ajvondrak/remote_ip\"}\n    }\n  end\n\n  defp deps do\n    [\n      {:combine, \"~> 0.10\"},\n      {:plug, \"~> 1.14\"},\n      {:ex_doc, \"~> 0.34\", only: :dev, runtime: false},\n      {:dialyxir, \"~> 1.4\", only: [:ci, :dev], runtime: false},\n      {:excoveralls, \"~> 0.18\", only: [:ci, :test], runtime: false},\n      {:castore, \"~> 1.0\", only: [:ci, :test], runtime: false}\n    ]\n  end\n\n  defp aliases do\n    [integrate: \"run integration/tests.exs\"]\n  end\n\n  defp dialyzer do\n    [plt_file: {:no_warn, \"priv/plts/dialyzer.plt\"}]\n  end\n\n  defp docs do\n    [\n      source_url: \"https://github.com/ajvondrak/remote_ip\",\n      main: \"RemoteIp\",\n      extras: [\"extras/algorithm.md\"]\n    ]\n  end\n\n  defp test_coverage() do\n    [tool: ExCoveralls]\n  end\nend\n"
  },
  {
    "path": "test/.formatter.exs",
    "content": "[\n  inputs: [\"**/*.exs\"],\n  import_deps: [:plug],\n\n  # This is an arbitrarily long line length. While most of the code conforms to\n  # an 80-character limit, many of the parsing tests involve gnarly strings &\n  # IP tuples that are just nicer to have on a single line. There's no good way\n  # of expressing this at a finer granularity (e.g., flagging specific sections\n  # of code), so we just let the tests get away with murder in general.\n  line_length: 800\n]\n"
  },
  {
    "path": "test/remote_ip/block_test.exs",
    "content": "defmodule RemoteIp.BlockTest do\n  use ExUnit.Case, async: true\n  import Bitwise\n\n  alias RemoteIp.Block\n\n  def octets(n) do\n    Stream.repeatedly(fn -> Enum.random(0..255) end) |> Enum.take(n)\n  end\n\n  def ipv4(octets) do\n    octets |> Enum.join(\".\")\n  end\n\n  def hextets(n) do\n    Stream.repeatedly(fn -> Enum.random(0..65_535) end) |> Enum.take(n)\n  end\n\n  def ipv6(hextets) do\n    hextets |> Enum.map(&Integer.to_string(&1, 16)) |> Enum.join(\":\")\n  end\n\n  test \"parse vs parse!\" do\n    {:ok, success} = Block.parse(\"127.0.0.1\")\n    assert Block.parse!(\"127.0.0.1\") == success\n\n    {:error, error} = Block.parse(\"127001\")\n    assert_raise ArgumentError, error, fn -> Block.parse!(\"127001\") end\n  end\n\n  test \"parsing invalid CIDR\" do\n    assert_raise ArgumentError, ~S'Invalid address \"invalid\" in CIDR \"invalid\"', fn ->\n      Block.parse!(\"invalid\")\n    end\n  end\n\n  test \"IPv4 block to string\" do\n    assert \"3.14.15.92/32\" == Block.parse!(\"3.14.15.92/32\") |> to_string()\n    assert \"3.14.15.0/24\" == Block.parse!(\"3.14.15.92/24\") |> to_string()\n    assert \"3.14.0.0/16\" == Block.parse!(\"3.14.15.92/16\") |> to_string()\n    assert \"3.0.0.0/8\" == Block.parse!(\"3.14.15.92/8\") |> to_string()\n    assert \"0.0.0.0/0\" == Block.parse!(\"3.14.15.92/0\") |> to_string()\n  end\n\n  test \"IPv6 block to string\" do\n    assert \"123::456/128\" == Block.parse!(\"123::456/128\") |> to_string()\n    assert \"123::/64\" == Block.parse!(\"123::456/64\") |> to_string()\n    assert \"::/0\" == Block.parse!(\"123::456/0\") |> to_string()\n  end\n\n  describe \"parsing IPv4\" do\n    test \"invalid address and prefix\" do\n      assert_raise ArgumentError, ~S'Invalid address \"3.14\" in CIDR \"3.14/159\"', fn ->\n        Block.parse!(\"3.14/159\")\n      end\n    end\n\n    test \"invalid address\" do\n      assert_raise ArgumentError, ~S'Invalid address \"3.141.592.6\" in CIDR \"3.141.592.6/5\"', fn ->\n        Block.parse!(\"3.141.592.6/5\")\n      end\n    end\n\n    test \"invalid prefix\" do\n      assert_raise ArgumentError, ~S'Invalid prefix \"invalid\" in CIDR \"0.0.0.0/invalid\"', fn ->\n        Block.parse!(\"0.0.0.0/invalid\")\n      end\n    end\n\n    test \"negative prefix\" do\n      assert_raise ArgumentError, ~S'Invalid prefix -1 in CIDR \"0.0.0.0/-1\"', fn ->\n        Block.parse!(\"0.0.0.0/-1\")\n      end\n    end\n\n    test \"oversized prefix\" do\n      assert_raise ArgumentError, ~S'Invalid prefix 33 in CIDR \"0.0.0.0/33\"', fn ->\n        Block.parse!(\"0.0.0.0/33\")\n      end\n    end\n\n    test \"address sans prefix\" do\n      ip = ipv4(octets(4))\n      assert Block.parse!(ip) == Block.parse!(\"#{ip}/32\")\n    end\n\n    test \"address with zero prefix\" do\n      ip = ipv4(octets(4))\n      assert %Block{proto: :v4, net: 0} = Block.parse!(\"#{ip}/0\")\n    end\n\n    test \"addresses with valid prefixes\" do\n      [a, b, c, d] = octets(4)\n      ip = ipv4([a, b, c, d])\n\n      {:v4, net_a} = Block.encode({a, 0, 0, 0})\n      {:v4, net_b} = Block.encode({a, b, 0, 0})\n      {:v4, net_c} = Block.encode({a, b, c, 0})\n      {:v4, net_x} = Block.encode({a, b &&& 0b11110000, 0, 0})\n\n      assert %Block{proto: :v4, net: ^net_a} = Block.parse!(\"#{ip}/8\")\n      assert %Block{proto: :v4, net: ^net_b} = Block.parse!(\"#{ip}/16\")\n      assert %Block{proto: :v4, net: ^net_c} = Block.parse!(\"#{ip}/24\")\n      assert %Block{proto: :v4, net: ^net_x} = Block.parse!(\"#{ip}/12\")\n    end\n\n    test \"masks\" do\n      assert %Block{mask: 0b00000000000000000000000000000000} = Block.parse!(\"0.0.0.0/0\")\n      assert %Block{mask: 0b10000000000000000000000000000000} = Block.parse!(\"0.0.0.0/1\")\n      assert %Block{mask: 0b11000000000000000000000000000000} = Block.parse!(\"0.0.0.0/2\")\n      assert %Block{mask: 0b11100000000000000000000000000000} = Block.parse!(\"0.0.0.0/3\")\n      assert %Block{mask: 0b11110000000000000000000000000000} = Block.parse!(\"0.0.0.0/4\")\n      assert %Block{mask: 0b11111000000000000000000000000000} = Block.parse!(\"0.0.0.0/5\")\n      assert %Block{mask: 0b11111100000000000000000000000000} = Block.parse!(\"0.0.0.0/6\")\n      assert %Block{mask: 0b11111110000000000000000000000000} = Block.parse!(\"0.0.0.0/7\")\n      assert %Block{mask: 0b11111111000000000000000000000000} = Block.parse!(\"0.0.0.0/8\")\n      assert %Block{mask: 0b11111111100000000000000000000000} = Block.parse!(\"0.0.0.0/9\")\n      assert %Block{mask: 0b11111111110000000000000000000000} = Block.parse!(\"0.0.0.0/10\")\n      assert %Block{mask: 0b11111111111000000000000000000000} = Block.parse!(\"0.0.0.0/11\")\n      assert %Block{mask: 0b11111111111100000000000000000000} = Block.parse!(\"0.0.0.0/12\")\n      assert %Block{mask: 0b11111111111110000000000000000000} = Block.parse!(\"0.0.0.0/13\")\n      assert %Block{mask: 0b11111111111111000000000000000000} = Block.parse!(\"0.0.0.0/14\")\n      assert %Block{mask: 0b11111111111111100000000000000000} = Block.parse!(\"0.0.0.0/15\")\n      assert %Block{mask: 0b11111111111111110000000000000000} = Block.parse!(\"0.0.0.0/16\")\n      assert %Block{mask: 0b11111111111111111000000000000000} = Block.parse!(\"0.0.0.0/17\")\n      assert %Block{mask: 0b11111111111111111100000000000000} = Block.parse!(\"0.0.0.0/18\")\n      assert %Block{mask: 0b11111111111111111110000000000000} = Block.parse!(\"0.0.0.0/19\")\n      assert %Block{mask: 0b11111111111111111111000000000000} = Block.parse!(\"0.0.0.0/20\")\n      assert %Block{mask: 0b11111111111111111111100000000000} = Block.parse!(\"0.0.0.0/21\")\n      assert %Block{mask: 0b11111111111111111111110000000000} = Block.parse!(\"0.0.0.0/22\")\n      assert %Block{mask: 0b11111111111111111111111000000000} = Block.parse!(\"0.0.0.0/23\")\n      assert %Block{mask: 0b11111111111111111111111100000000} = Block.parse!(\"0.0.0.0/24\")\n      assert %Block{mask: 0b11111111111111111111111110000000} = Block.parse!(\"0.0.0.0/25\")\n      assert %Block{mask: 0b11111111111111111111111111000000} = Block.parse!(\"0.0.0.0/26\")\n      assert %Block{mask: 0b11111111111111111111111111100000} = Block.parse!(\"0.0.0.0/27\")\n      assert %Block{mask: 0b11111111111111111111111111110000} = Block.parse!(\"0.0.0.0/28\")\n      assert %Block{mask: 0b11111111111111111111111111111000} = Block.parse!(\"0.0.0.0/29\")\n      assert %Block{mask: 0b11111111111111111111111111111100} = Block.parse!(\"0.0.0.0/30\")\n      assert %Block{mask: 0b11111111111111111111111111111110} = Block.parse!(\"0.0.0.0/31\")\n      assert %Block{mask: 0b11111111111111111111111111111111} = Block.parse!(\"0.0.0.0/32\")\n    end\n\n    test \"reserved ranges\" do\n      assert Block.parse!(\"127.0.0.0/8\") == %Block{\n               proto: :v4,\n               net: :binary.decode_unsigned(<<127, 0, 0, 0>>),\n               mask: :binary.decode_unsigned(<<255, 0, 0, 0>>)\n             }\n\n      assert Block.parse!(\"10.0.0.0/8\") == %Block{\n               proto: :v4,\n               net: :binary.decode_unsigned(<<10, 0, 0, 0>>),\n               mask: :binary.decode_unsigned(<<255, 0, 0, 0>>)\n             }\n\n      assert Block.parse!(\"172.16.0.0/12\") == %Block{\n               proto: :v4,\n               net: :binary.decode_unsigned(<<172, 16, 0, 0>>),\n               mask: :binary.decode_unsigned(<<255, 240, 0, 0>>)\n             }\n\n      assert Block.parse!(\"192.168.0.0/16\") == %Block{\n               proto: :v4,\n               net: :binary.decode_unsigned(<<192, 168, 0, 0>>),\n               mask: :binary.decode_unsigned(<<255, 255, 0, 0>>)\n             }\n    end\n  end\n\n  describe \"parsing IPv6\" do\n    test \"invalid address and prefix\" do\n      assert_raise ArgumentError, ~S'Invalid address \"a:b:c\" in CIDR \"a:b:c/1/2/3\"', fn ->\n        Block.parse!(\"a:b:c/1/2/3\")\n      end\n    end\n\n    test \"invalid address\" do\n      assert_raise ArgumentError, ~S'Invalid address \"f7::u\" in CIDR \"f7::u/12\"', fn ->\n        Block.parse!(\"f7::u/12\")\n      end\n    end\n\n    test \"invalid prefix\" do\n      assert_raise ArgumentError, ~S'Invalid prefix \"1/2/3\" in CIDR \"::a:b:c/1/2/3\"', fn ->\n        Block.parse!(\"::a:b:c/1/2/3\")\n      end\n    end\n\n    test \"negative prefix\" do\n      assert_raise ArgumentError, ~S'Invalid prefix -1 in CIDR \"::/-1\"', fn ->\n        Block.parse!(\"::/-1\")\n      end\n    end\n\n    test \"oversized prefix\" do\n      assert_raise ArgumentError, ~S'Invalid prefix 129 in CIDR \"::/129\"', fn ->\n        Block.parse!(\"::/129\")\n      end\n    end\n\n    test \"address sans prefix\" do\n      ip = ipv6(hextets(8))\n      assert Block.parse!(ip) == Block.parse!(\"#{ip}/128\")\n    end\n\n    test \"address with zero prefix\" do\n      ip = ipv6(hextets(8))\n      assert %Block{proto: :v6, net: 0} = Block.parse!(\"#{ip}/0\")\n    end\n\n    test \"addresses with valid prefixes\" do\n      [a, b, c, d, e, f, g, h] = hextets(8)\n      ip = ipv6([a, b, c, d, e, f, g, h])\n\n      {:v6, net_a} = Block.encode({a, 0, 0, 0, 0, 0, 0, 0})\n      {:v6, net_b} = Block.encode({a, b, 0, 0, 0, 0, 0, 0})\n      {:v6, net_c} = Block.encode({a, b, c, 0, 0, 0, 0, 0})\n      {:v6, net_d} = Block.encode({a, b, c, d, 0, 0, 0, 0})\n      {:v6, net_e} = Block.encode({a, b, c, d, e, 0, 0, 0})\n      {:v6, net_f} = Block.encode({a, b, c, d, e, f, 0, 0})\n      {:v6, net_g} = Block.encode({a, b, c, d, e, f, g, 0})\n      {:v6, net_x} = Block.encode({a, b, c, d, e &&& 0b1000000000000000, 0, 0, 0})\n\n      assert %Block{proto: :v6, net: ^net_a} = Block.parse!(\"#{ip}/16\")\n      assert %Block{proto: :v6, net: ^net_b} = Block.parse!(\"#{ip}/32\")\n      assert %Block{proto: :v6, net: ^net_c} = Block.parse!(\"#{ip}/48\")\n      assert %Block{proto: :v6, net: ^net_d} = Block.parse!(\"#{ip}/64\")\n      assert %Block{proto: :v6, net: ^net_e} = Block.parse!(\"#{ip}/80\")\n      assert %Block{proto: :v6, net: ^net_f} = Block.parse!(\"#{ip}/96\")\n      assert %Block{proto: :v6, net: ^net_g} = Block.parse!(\"#{ip}/112\")\n      assert %Block{proto: :v6, net: ^net_x} = Block.parse!(\"#{ip}/65\")\n    end\n\n    test \"masks\" do\n      assert %Block{mask: 0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/0\")\n      assert %Block{mask: 0b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/1\")\n      assert %Block{mask: 0b11000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/2\")\n      assert %Block{mask: 0b11100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/3\")\n      assert %Block{mask: 0b11110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/4\")\n      assert %Block{mask: 0b11111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/5\")\n      assert %Block{mask: 0b11111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/6\")\n      assert %Block{mask: 0b11111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/7\")\n      assert %Block{mask: 0b11111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/8\")\n      assert %Block{mask: 0b11111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/9\")\n      assert %Block{mask: 0b11111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/10\")\n      assert %Block{mask: 0b11111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/11\")\n      assert %Block{mask: 0b11111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/12\")\n      assert %Block{mask: 0b11111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/13\")\n      assert %Block{mask: 0b11111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/14\")\n      assert %Block{mask: 0b11111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/15\")\n      assert %Block{mask: 0b11111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/16\")\n      assert %Block{mask: 0b11111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/17\")\n      assert %Block{mask: 0b11111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/18\")\n      assert %Block{mask: 0b11111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/19\")\n      assert %Block{mask: 0b11111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/20\")\n      assert %Block{mask: 0b11111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/21\")\n      assert %Block{mask: 0b11111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/22\")\n      assert %Block{mask: 0b11111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/23\")\n      assert %Block{mask: 0b11111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/24\")\n      assert %Block{mask: 0b11111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/25\")\n      assert %Block{mask: 0b11111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/26\")\n      assert %Block{mask: 0b11111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/27\")\n      assert %Block{mask: 0b11111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/28\")\n      assert %Block{mask: 0b11111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/29\")\n      assert %Block{mask: 0b11111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/30\")\n      assert %Block{mask: 0b11111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/31\")\n      assert %Block{mask: 0b11111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/32\")\n      assert %Block{mask: 0b11111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/33\")\n      assert %Block{mask: 0b11111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/34\")\n      assert %Block{mask: 0b11111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/35\")\n      assert %Block{mask: 0b11111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/36\")\n      assert %Block{mask: 0b11111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/37\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/38\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/39\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/40\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/41\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/42\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/43\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/44\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/45\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/46\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/47\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/48\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/49\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/50\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/51\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/52\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/53\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/54\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/55\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/56\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/57\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/58\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/59\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/60\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/61\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/62\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/63\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/64\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/65\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/66\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/67\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/68\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/69\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/70\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/71\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/72\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/73\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/74\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/75\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/76\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000} = Block.parse!(\"::/77\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000} = Block.parse!(\"::/78\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000} = Block.parse!(\"::/79\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000} = Block.parse!(\"::/80\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000} = Block.parse!(\"::/81\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000} = Block.parse!(\"::/82\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000} = Block.parse!(\"::/83\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000} = Block.parse!(\"::/84\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000} = Block.parse!(\"::/85\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000} = Block.parse!(\"::/86\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000} = Block.parse!(\"::/87\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000} = Block.parse!(\"::/88\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000} = Block.parse!(\"::/89\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000} = Block.parse!(\"::/90\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000} = Block.parse!(\"::/91\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000} = Block.parse!(\"::/92\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000} = Block.parse!(\"::/93\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000} = Block.parse!(\"::/94\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000} = Block.parse!(\"::/95\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000} = Block.parse!(\"::/96\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000} = Block.parse!(\"::/97\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000} = Block.parse!(\"::/98\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000} = Block.parse!(\"::/99\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000} = Block.parse!(\"::/100\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000} = Block.parse!(\"::/101\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000} = Block.parse!(\"::/102\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000} = Block.parse!(\"::/103\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000} = Block.parse!(\"::/104\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000} = Block.parse!(\"::/105\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000} = Block.parse!(\"::/106\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000} = Block.parse!(\"::/107\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000} = Block.parse!(\"::/108\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000} = Block.parse!(\"::/109\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000} = Block.parse!(\"::/110\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000} = Block.parse!(\"::/111\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000} = Block.parse!(\"::/112\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000} = Block.parse!(\"::/113\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000} = Block.parse!(\"::/114\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000} = Block.parse!(\"::/115\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000} = Block.parse!(\"::/116\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000} = Block.parse!(\"::/117\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000} = Block.parse!(\"::/118\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000} = Block.parse!(\"::/119\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000} = Block.parse!(\"::/120\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000} = Block.parse!(\"::/121\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000} = Block.parse!(\"::/122\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000} = Block.parse!(\"::/123\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000} = Block.parse!(\"::/124\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000} = Block.parse!(\"::/125\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100} = Block.parse!(\"::/126\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110} = Block.parse!(\"::/127\")\n      assert %Block{mask: 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111} = Block.parse!(\"::/128\")\n    end\n\n    test \"reserved ranges\" do\n      assert Block.parse!(\"::1/128\") == %Block{\n               proto: :v6,\n               net: 0x00000000000000000000000000000001,\n               mask: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n             }\n\n      assert Block.parse!(\"fc00::/7\") == %Block{\n               proto: :v6,\n               net: 0xFC000000000000000000000000000000,\n               mask: 0xFE000000000000000000000000000000\n             }\n    end\n  end\n\n  describe \"IPv4 membership\" do\n    test \"inside block\" do\n      block = Block.parse!(\"192.168.0.0/16\")\n\n      for a <- 0..255, b <- 0..255 do\n        assert Block.contains?(block, Block.encode({192, 168, a, b}))\n      end\n    end\n\n    test \"outside block\" do\n      block = Block.parse!(\"192.168.0.0/16\")\n      refute Block.contains?(block, Block.encode({192, 167, 255, 255}))\n      refute Block.contains?(block, Block.encode({192, 169, 0, 0}))\n      refute Block.contains?(block, Block.encode({191, 168, 0, 0}))\n      refute Block.contains?(block, Block.encode({191, 255, 255, 255}))\n      refute Block.contains?(block, Block.encode({194, 0, 0, 0}))\n      refute Block.contains?(block, Block.encode({31, 41, 59, 27}))\n    end\n\n    test \"with exact match\" do\n      block = Block.parse!(\"127.0.0.1/32\")\n      refute Block.contains?(block, Block.encode({127, 0, 0, 0}))\n      assert Block.contains?(block, Block.encode({127, 0, 0, 1}))\n      refute Block.contains?(block, Block.encode({127, 0, 0, 2}))\n    end\n\n    test \"with zero-length prefix\" do\n      block = Block.parse!(\"0.0.0.0/0\")\n      [a, b, c, d] = octets(4)\n      assert Block.contains?(block, Block.encode({a, b, c, d}))\n    end\n\n    test \"with lower bits that are masked off\" do\n      block = Block.parse!(\"192.168.100.14/24\")\n      assert block.net == :binary.decode_unsigned(<<192, 168, 100, 0>>)\n\n      for member <- 0..255 do\n        assert Block.contains?(block, Block.encode({192, 168, 100, member}))\n      end\n    end\n\n    test \"against IPv6\" do\n      block = Block.parse!(\"0.0.0.0/0\")\n      [a, b, c, d, e, f, g, h] = hextets(8)\n      refute Block.contains?(block, Block.encode({a, b, c, d, e, f, g, h}))\n    end\n  end\n\n  describe \"IPv6 membership\" do\n    test \"inside block\" do\n      block = Block.parse!(\"1111:2222:3333:4444:5555:6666:7777:8800/120\")\n\n      for member <- 0x8800..0x88FF do\n        assert Block.contains?(block, Block.encode({0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, member}))\n      end\n    end\n\n    test \"outside block\" do\n      block = Block.parse!(\"1111:2222:3333:4444:5555:6666:7777:8800/120\")\n      refute Block.contains?(block, Block.encode({0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x87FF}))\n      refute Block.contains?(block, Block.encode({0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8900}))\n    end\n\n    test \"with exact match\" do\n      block = Block.parse!(\"::1/128\")\n      refute Block.contains?(block, Block.encode({0, 0, 0, 0, 0, 0, 0, 0}))\n      assert Block.contains?(block, Block.encode({0, 0, 0, 0, 0, 0, 0, 1}))\n      refute Block.contains?(block, Block.encode({0, 0, 0, 0, 0, 0, 0, 2}))\n    end\n\n    test \"with zero-length prefix\" do\n      block = Block.parse!(\"::/0\")\n      [a, b, c, d, e, f, g, h] = hextets(8)\n      assert Block.contains?(block, Block.encode({a, b, c, d, e, f, g, h}))\n    end\n\n    test \"with lower bits that are masked off\" do\n      block = Block.parse!(\"a:b:c:d:e:f::/48\")\n      assert block.net == :binary.decode_unsigned(<<0x000A::16, 0x000B::16, 0x00C::16, 0::16, 0::16, 0::16, 0::16, 0::16>>)\n      [d, e, f, g, h] = hextets(5)\n      assert Block.contains?(block, Block.encode({0x000A, 0x000B, 0x000C, d, e, f, g, h}))\n    end\n\n    test \"against IPv4\" do\n      block = Block.parse!(\"::/0\")\n      [a, b, c, d] = octets(4)\n      refute Block.contains?(block, Block.encode({a, b, c, d}))\n    end\n  end\nend\n"
  },
  {
    "path": "test/remote_ip/headers_test.exs",
    "content": "defmodule RemoteIp.HeadersTest do\n  use ExUnit.Case, async: true\n\n  doctest RemoteIp.Headers\n\n  test \"taking from an empty list of headers\" do\n    headers = []\n    allowed = [\"a\", \"b\", \"c\"]\n    assert RemoteIp.Headers.take(headers, allowed) == []\n  end\n\n  test \"taking no headers\" do\n    headers = [{\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}]\n    allowed = []\n    assert RemoteIp.Headers.take(headers, allowed) == []\n  end\n\n  test \"taking all headers\" do\n    headers = [{\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}]\n    allowed = [\"a\", \"b\", \"c\"]\n    assert RemoteIp.Headers.take(headers, allowed) == headers\n  end\n\n  test \"taking a subset of headers\" do\n    headers = [{\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}]\n    allowed = [\"a\", \"c\"]\n    assert RemoteIp.Headers.take(headers, allowed) == [{\"a\", \"1\"}, {\"c\", \"3\"}]\n  end\n\n  test \"taking a superset of headers\" do\n    headers = [{\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}]\n    allowed = [\"a\", \"z\"]\n    assert RemoteIp.Headers.take(headers, allowed) == [{\"a\", \"1\"}]\n  end\n\n  test \"taking a disjoint set of headers\" do\n    headers = [{\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}]\n    allowed = [\"x\", \"y\", \"z\"]\n    assert RemoteIp.Headers.take(headers, allowed) == []\n  end\n\n  test \"taking duplicate headers\" do\n    headers = [{\"a\", \"1\"}, {\"a\", \"2\"}, {\"b\", \"3\"}]\n    allowed = [\"a\"]\n    assert RemoteIp.Headers.take(headers, allowed) == [{\"a\", \"1\"}, {\"a\", \"2\"}]\n  end\n\n  test \"parsing Forwarded headers\" do\n    ips = [\n      {1, 2, 3, 4},\n      {0, 0, 0, 0, 2, 3, 4, 5},\n      {3, 4, 5, 6},\n      {0, 0, 0, 0, 4, 5, 6, 7}\n    ]\n\n    headers = [\n      {\"forwarded\", ~S'for=1.2.3.4'},\n      {\"forwarded\", ~S'for=\"[::2:3:4:5]\";proto=http;host=example.com'},\n      {\"forwarded\", ~S'proto=http;for=3.4.5.6;by=127.0.0.1'},\n      {\"forwarded\", ~S'proto=http;host=example.com;for=\"[::4:5:6:7]\"'}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n\n    headers = [\n      {\"forwarded\", ~S'for=1.2.3.4, for=\"[::2:3:4:5]\";proto=http;host=example.com'},\n      {\"forwarded\", ~S'proto=http;for=3.4.5.6;by=127.0.0.1'},\n      {\"forwarded\", ~S'proto=http;host=example.com;for=\"[::4:5:6:7]\"'}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n\n    headers = [\n      {\"forwarded\", ~S'for=1.2.3.4, for=\"[::2:3:4:5]\";proto=http;host=example.com, proto=http;for=3.4.5.6;by=127.0.0.1'},\n      {\"forwarded\", ~S'proto=http;host=example.com;for=\"[::4:5:6:7]\"'}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n\n    headers = [\n      {\"forwarded\", ~S'for=1.2.3.4'},\n      {\"forwarded\", ~S'for=\"[::2:3:4:5]\";proto=http;host=example.com'},\n      {\"forwarded\", ~S'proto=http;for=3.4.5.6;by=127.0.0.1, proto=http;host=example.com;for=\"[::4:5:6:7]\"'}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n\n    headers = [\n      {\"forwarded\", ~S'for=1.2.3.4'},\n      {\"forwarded\", ~S'for=\"[::2:3:4:5]\";proto=http;host=example.com, proto=http;for=3.4.5.6;by=127.0.0.1, proto=http;host=example.com;for=\"[::4:5:6:7]\"'}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n  end\n\n  test \"parsing generic headers\" do\n    headers = [\n      {\"generic\", \"1.1.1.1, unknown, 2.2.2.2\"},\n      {\"generic\", \"   3.3.3.3 ,  4.4.4.4,not_an_ip\"},\n      {\"generic\", \"5.5.5.5,::6:6:6:6\"},\n      {\"generic\", \"unknown,5,7.7.7.7\"}\n    ]\n\n    ips = [\n      {1, 1, 1, 1},\n      {2, 2, 2, 2},\n      {3, 3, 3, 3},\n      {4, 4, 4, 4},\n      {5, 5, 5, 5},\n      {0, 0, 0, 0, 6, 6, 6, 6},\n      {7, 7, 7, 7}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n  end\n\n  test \"parsing an unrecognized header falls back to generic parsing\" do\n    headers = [\n      {\"x-forwarded-for\", \"1.1.1.1,2.2.2.2\"},\n      {\"x-real-ip\", \"3.3.3.3, 4.4.4.4\"},\n      {\"x-client-ip\", \"5.5.5.5\"}\n    ]\n\n    ips = [\n      {1, 1, 1, 1},\n      {2, 2, 2, 2},\n      {3, 3, 3, 3},\n      {4, 4, 4, 4},\n      {5, 5, 5, 5}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n  end\n\n  test \"parsing multiple kinds of headers\" do\n    headers = [\n      {\"forwarded\", \"for=1.1.1.1\"},\n      {\"x-forwarded-for\", \"2.2.2.2\"},\n      {\"forwarded\", \"for=3.3.3.3, for=4.4.4.4\"},\n      {\"x-forwarded-for\", \"invalid\"},\n      {\"forwarded\", \"for=5.5.5.5\"},\n      {\"x-forwarded-for\", \"6.6.6.6, 7.7.7.7\"},\n      {\"invalid\", \"header\"}\n    ]\n\n    ips = [\n      {1, 1, 1, 1},\n      {2, 2, 2, 2},\n      {3, 3, 3, 3},\n      {4, 4, 4, 4},\n      {5, 5, 5, 5},\n      {6, 6, 6, 6},\n      {7, 7, 7, 7}\n    ]\n\n    assert RemoteIp.Headers.parse(headers) == ips\n  end\n\n  defmodule Custom do\n    @behaviour RemoteIp.Parser\n\n    @impl RemoteIp.Parser\n\n    def parse(_) do\n      [{1, 2, 3, 4}]\n    end\n  end\n\n  test \"using custom parsers\" do\n    headers = [\n      {\"x-custom\", \"parser's gonna parse\"},\n      {\"x-forwarded-for\", \"2.3.4.5\"},\n      {\"forwarded\", \"for=3.4.5.6\"}\n    ]\n\n    ips = [\n      {1, 2, 3, 4},\n      {2, 3, 4, 5}\n    ]\n\n    assert RemoteIp.Headers.parse(headers, %{\"x-custom\" => Custom}) == ips\n  end\nend\n"
  },
  {
    "path": "test/remote_ip/options_test.exs",
    "content": "defmodule RemoteIp.OptionsTest do\n  use ExUnit.Case, async: true\n\n  defmodule MFA do\n    use Agent\n\n    def setup do\n      {:ok, _} = Agent.start_link(fn -> [] end, name: __MODULE__)\n      :ok\n    end\n\n    def get(opt) do\n      Agent.get(__MODULE__, fn opts -> Keyword.get(opts, opt) end)\n    end\n\n    def put(opt, val) do\n      Agent.update(__MODULE__, fn opts -> Keyword.put(opts, opt, val) end)\n    end\n  end\n\n  setup do\n    MFA.setup()\n  end\n\n  describe \"pack\" do\n    test \"unknown option\" do\n      packed = RemoteIp.Options.pack(unknown: :option)\n      refute Keyword.has_key?(packed, :unknown)\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :proxies)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":headers default\" do\n      packed = RemoteIp.Options.pack([])\n      assert \"forwarded\" in packed[:headers]\n      assert \"x-forwarded-for\" in packed[:headers]\n      assert \"x-client-ip\" in packed[:headers]\n      assert \"x-real-ip\" in packed[:headers]\n    end\n\n    test \":headers list\" do\n      packed = RemoteIp.Options.pack(headers: ~w[a b c])\n      assert packed[:headers] == ~w[a b c]\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :proxies)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":headers mfa\" do\n      packed = RemoteIp.Options.pack(headers: {MFA, :get, [:headers]})\n      assert packed[:headers] == {MFA, :get, [:headers]}\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :proxies)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":parsers map\" do\n      packed = RemoteIp.Options.pack(parsers: %{\"foo\" => Bar})\n      assert is_map(packed[:parsers])\n      assert packed[:parsers][\"foo\"] == Bar\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :proxies)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":parsers default\" do\n      packed = RemoteIp.Options.pack([])\n      assert is_map(packed[:parsers])\n      assert packed[:parsers][\"forwarded\"] == RemoteIp.Parsers.Forwarded\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :proxies)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":parsers mfa\" do\n      packed = RemoteIp.Options.pack(parsers: {MFA, :get, [:parsers]})\n      assert packed[:parsers] == {MFA, :get, [:parsers]}\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :proxies)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":proxies default\" do\n      packed = RemoteIp.Options.pack([])\n      assert packed[:proxies] == []\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":proxies list\" do\n      packed = RemoteIp.Options.pack(proxies: ~w[123.0.0.0/8])\n      assert [%RemoteIp.Block{} = block] = packed[:proxies]\n      assert to_string(block) == \"123.0.0.0/8\"\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":proxies mfa\" do\n      packed = RemoteIp.Options.pack(proxies: {MFA, :get, [:proxies]})\n      assert packed[:proxies] == {MFA, :get, [:proxies]}\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :clients)\n    end\n\n    test \":clients default\" do\n      packed = RemoteIp.Options.pack([])\n      assert packed[:clients] == []\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :proxies)\n    end\n\n    test \":clients list\" do\n      packed = RemoteIp.Options.pack(clients: ~w[234.0.0.0/8])\n      assert [%RemoteIp.Block{} = block] = packed[:clients]\n      assert to_string(block) == \"234.0.0.0/8\"\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :proxies)\n    end\n\n    test \":clients mfa\" do\n      packed = RemoteIp.Options.pack(clients: {MFA, :get, [:clients]})\n      assert packed[:clients] == {MFA, :get, [:clients]}\n      assert Keyword.has_key?(packed, :headers)\n      assert Keyword.has_key?(packed, :parsers)\n      assert Keyword.has_key?(packed, :proxies)\n    end\n  end\n\n  describe \"unpack\" do\n    test \":headers default\" do\n      packed = RemoteIp.Options.pack([])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:headers] == packed[:headers]\n    end\n\n    test \":headers list\" do\n      packed = RemoteIp.Options.pack(headers: ~w[a b c])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:headers] == packed[:headers]\n    end\n\n    test \":headers mfa\" do\n      packed = RemoteIp.Options.pack(headers: {MFA, :get, [:headers]})\n\n      MFA.put(:headers, ~w[a b c])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:headers] == ~w[a b c]\n\n      MFA.put(:headers, ~w[d e f])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:headers] == ~w[d e f]\n    end\n\n    test \":parsers default\" do\n      packed = RemoteIp.Options.pack([])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:parsers] == packed[:parsers]\n    end\n\n    test \":parsers map\" do\n      packed = RemoteIp.Options.pack(parsers: %{\"foo\" => Bar})\n      unpacked = RemoteIp.Options.unpack(packed)\n      parsers = %{\"forwarded\" => RemoteIp.Parsers.Forwarded, \"foo\" => Bar}\n      assert unpacked[:parsers] == parsers\n    end\n\n    test \":parsers mfa\" do\n      packed = RemoteIp.Options.pack(parsers: {MFA, :get, [:parsers]})\n\n      MFA.put(:parsers, %{\"foo\" => Bar})\n      unpacked = RemoteIp.Options.unpack(packed)\n      parsers = %{\"forwarded\" => RemoteIp.Parsers.Forwarded, \"foo\" => Bar}\n      assert unpacked[:parsers] == parsers\n\n      MFA.put(:parsers, %{\"bar\" => Baz})\n      unpacked = RemoteIp.Options.unpack(packed)\n      parsers = %{\"forwarded\" => RemoteIp.Parsers.Forwarded, \"bar\" => Baz}\n      assert unpacked[:parsers] == parsers\n    end\n\n    test \":proxies default\" do\n      packed = RemoteIp.Options.pack([])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:proxies] == packed[:proxies]\n    end\n\n    test \":proxies list\" do\n      packed = RemoteIp.Options.pack(proxies: ~w[123.0.0.0/8 234.0.0.0/8])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:proxies] == packed[:proxies]\n    end\n\n    test \":proxies mfa\" do\n      packed = RemoteIp.Options.pack(proxies: {MFA, :get, [:proxies]})\n\n      MFA.put(:proxies, ~w[123.0.0.0/8])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert [%RemoteIp.Block{} = block] = unpacked[:proxies]\n      assert to_string(block) == \"123.0.0.0/8\"\n\n      MFA.put(:proxies, ~w[234.0.0.0/8])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert [%RemoteIp.Block{} = block] = unpacked[:proxies]\n      assert to_string(block) == \"234.0.0.0/8\"\n    end\n\n    test \":clients default\" do\n      packed = RemoteIp.Options.pack([])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:clients] == packed[:clients]\n    end\n\n    test \":clients list\" do\n      packed = RemoteIp.Options.pack(clients: ~w[123.0.0.0/8 234.0.0.0/8])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert unpacked[:clients] == packed[:clients]\n    end\n\n    test \":clients mfa\" do\n      packed = RemoteIp.Options.pack(clients: {MFA, :get, [:clients]})\n\n      MFA.put(:clients, ~w[123.0.0.0/8])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert [%RemoteIp.Block{} = block] = unpacked[:clients]\n      assert to_string(block) == \"123.0.0.0/8\"\n\n      MFA.put(:clients, ~w[234.0.0.0/8])\n      unpacked = RemoteIp.Options.unpack(packed)\n      assert [%RemoteIp.Block{} = block] = unpacked[:clients]\n      assert to_string(block) == \"234.0.0.0/8\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/remote_ip/parsers/forwarded_test.exs",
    "content": "defmodule RemoteIp.Parsers.ForwardedTest do\n  use ExUnit.Case, async: true\n\n  alias RemoteIp.Parsers.Forwarded\n\n  doctest Forwarded\n\n  describe \"parsing\" do\n    test \"RFC 7239 examples\" do\n      parsed = Forwarded.parse(~S'for=\"_gazonk\"')\n      assert parsed == []\n\n      parsed = Forwarded.parse(~S'For=\"[2001:db8:cafe::17]:4711\"')\n      assert parsed == [{8193, 3512, 51966, 0, 0, 0, 0, 23}]\n\n      parsed = Forwarded.parse(~S'for=192.0.2.60;proto=http;by=203.0.113.43')\n      assert parsed == [{192, 0, 2, 60}]\n\n      parsed = Forwarded.parse(~S'for=192.0.2.43, for=198.51.100.17')\n      assert parsed == [{192, 0, 2, 43}, {198, 51, 100, 17}]\n    end\n\n    test \"case insensitivity\" do\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'for=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'foR=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'fOr=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'fOR=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'For=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'FoR=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'FOr=0.0.0.0')\n      assert [{0, 0, 0, 0}] == Forwarded.parse(~S'FOR=0.0.0.0')\n\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'for=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'foR=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'fOr=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'fOR=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'For=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'FoR=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'FOr=\"[::]\"')\n      assert [{0, 0, 0, 0, 0, 0, 0, 0}] == Forwarded.parse(~S'FOR=\"[::]\"')\n    end\n\n    test \"IPv4\" do\n      assert [] == Forwarded.parse(~S'for=')\n      assert [] == Forwarded.parse(~S'for=1')\n      assert [] == Forwarded.parse(~S'for=1.2')\n      assert [] == Forwarded.parse(~S'for=1.2.3')\n      assert [] == Forwarded.parse(~S'for=1000.2.3.4')\n      assert [] == Forwarded.parse(~S'for=1.2000.3.4')\n      assert [] == Forwarded.parse(~S'for=1.2.3000.4')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4000')\n      assert [] == Forwarded.parse(~S'for=1abc.2.3.4')\n      assert [] == Forwarded.parse(~S'for=1.2abc.3.4')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4abc')\n      assert [] == Forwarded.parse(~S'for=1.2.3abc.4')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4abc')\n      assert [] == Forwarded.parse(~S'for=\"1.2.3.4')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4\"')\n\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=1.2.3.4')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"\\1.2\\.3.\\4\"')\n    end\n\n    test \"IPv4 with port\" do\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:1')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:12')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:123')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:1234')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:12345')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:123456')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:_underscore')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4:no_underscore')\n\n      assert [] == Forwarded.parse(~S'for=\"1.2.3.4:\"')\n      assert [] == Forwarded.parse(~S'for=\"1.2.3.4:123456\"')\n      assert [] == Forwarded.parse(~S'for=\"1.2.3.4:no_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"1.2\\.3.4\\:no_un\\der\\score\"')\n\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4:1\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4:12\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4:123\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4:1234\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4:12345\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"1.2.3.4:_underscore\"')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=\"\\1.2\\.3.4\\:_po\\r\\t\"')\n    end\n\n    test \"improperly formatted IPv6\" do\n      assert [] == Forwarded.parse(~S'for=[127.0.0.1]')\n      assert [] == Forwarded.parse(~S'for=\"[127.0.0.1]\"')\n\n      assert [] == Forwarded.parse(~S'for=::127.0.0.1')\n      assert [] == Forwarded.parse(~S'for=[::127.0.0.1]')\n      assert [] == Forwarded.parse(~S'for=\"::127.0.0.1\"')\n      assert [] == Forwarded.parse(~S'for=\"[::127.0.0.1\"')\n      assert [] == Forwarded.parse(~S'for=\"::127.0.0.1]\"')\n\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8\"')\n      assert [] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8]\"')\n    end\n\n    test \"IPv6 with port\" do\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:1')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:12')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:123')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:1234')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:12345')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:123456')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:_underscore')\n      assert [] == Forwarded.parse(~S'for=::1.2.3.4:no_underscore')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:1')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:12')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:123')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:1234')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:12345')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:123456')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:_underscore')\n      assert [] == Forwarded.parse(~S'for=[::1.2.3.4]:no_underscore')\n\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:123456\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:no_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2\\.3.4\\:no_un\\der\\score\"')\n      assert [] == Forwarded.parse(~S'for=\"[::1.2.3.4]:\"')\n      assert [] == Forwarded.parse(~S'for=\"[::1.2.3.4]:123456\"')\n      assert [] == Forwarded.parse(~S'for=\"[::1.2.3.4]:no_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"\\[::1.2\\.3.4]\\:no_un\\der\\score\"')\n\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:1\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:12\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:123\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:1234\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:12345\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2.3.4:_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"::\\1.2\\.3.4\\:_po\\r\\t\"')\n\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::1.2.3.4]:1\"')\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::1.2.3.4]:12\"')\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::1.2.3.4]:123\"')\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::1.2.3.4]:1234\"')\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::1.2.3.4]:12345\"')\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::1.2.3.4]:_underscore\"')\n      assert [{0, 0, 0, 0, 0, 0, 258, 772}] == Forwarded.parse(~S'for=\"[::\\1.2\\.3.4\\]\\:_po\\r\\t\"')\n\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:1')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:12')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:123')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:1234')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:12345')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:123456')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:_underscore')\n      assert [] == Forwarded.parse(~S'for=1:2:3:4:5:6:7:8:no_underscore')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:1')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:12')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:123')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:1234')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:12345')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:123456')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:_underscore')\n      assert [] == Forwarded.parse(~S'for=[1:2:3:4:5:6:7:8]:no_underscore')\n\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:123456\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:no_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"::1.2\\.3.4\\:no_un\\der\\score\"')\n      assert [] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:\"')\n      assert [] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:123456\"')\n      assert [] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:no_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"\\[1:2\\:3:4:5:6:7:8]\\:no_un\\der\\score\"')\n\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:1\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:12\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:123\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:1234\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:12345\"')\n      assert [] == Forwarded.parse(~S'for=\"1:2:3:4:5:6:7:8:_underscore\"')\n      assert [] == Forwarded.parse(~S'for=\"\\1:2\\:3:4:5:6:7:8\\:_po\\r\\t\"')\n\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:1\"')\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:12\"')\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:123\"')\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:1234\"')\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:12345\"')\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:5:6:7:8]:_underscore\"')\n      assert [{1, 2, 3, 4, 5, 6, 7, 8}] == Forwarded.parse(~S'for=\"[1:2:3:4:\\5:6\\:7:8\\]\\:_po\\r\\t\"')\n    end\n\n    test \"IPv6 without ::\" do\n      assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a:bc:def:d34d]\"')\n      assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a:bc:1.2.3.4]\"')\n    end\n\n    test \"IPv6 with :: at position 0\" do\n      assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[::23:456:7890:a:bc:def:d34d]\"')\n      assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[::23:456:7890:a:bc:1.2.3.4]\"')\n      assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[::456:7890:a:bc:def:d34d]\"')\n      assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[::456:7890:a:bc:1.2.3.4]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[::7890:a:bc:def:d34d]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[::7890:a:bc:1.2.3.4]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[::a:bc:def:d34d]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[::a:bc:1.2.3.4]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[::bc:def:d34d]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[::bc:1.2.3.4]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[::def:d34d]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[::1.2.3.4]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[::d34d]\"')\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[::]\"')\n    end\n\n    test \"IPv6 with :: at position 1\" do\n      assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1::456:7890:a:bc:def:d34d]\"')\n      assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1::456:7890:a:bc:1.2.3.4]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1::7890:a:bc:def:d34d]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1::7890:a:bc:1.2.3.4]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1::a:bc:def:d34d]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1::a:bc:1.2.3.4]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1::bc:def:d34d]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1::bc:1.2.3.4]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1::def:d34d]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1::1.2.3.4]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[1::d34d]\"')\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[1::]\"')\n    end\n\n    test \"IPv6 with :: at position 2\" do\n      assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23::7890:a:bc:def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23::7890:a:bc:1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23::a:bc:def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23::a:bc:1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23::bc:def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23::bc:1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23::def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23::1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23::d34d]\"')\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[1:23::]\"')\n    end\n\n    test \"IPv6 with :: at position 3\" do\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456::a:bc:def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456::a:bc:1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456::bc:def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456::bc:1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456::def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456::1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456::d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[1:23:456::]\"')\n    end\n\n    test \"IPv6 with :: at position 4\" do\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890::bc:def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00BC, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456:7890::bc:1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890::def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456:7890::1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890::d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[1:23:456:7890::]\"')\n    end\n\n    test \"IPv6 with :: at position 5\" do\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0DEF, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a::def:d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0102, 0x0304}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a::1.2.3.4]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a::d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a::]\"')\n    end\n\n    test \"IPv6 with :: at position 6\" do\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0000, 0xD34D}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a:bc::d34d]\"')\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[1:23:456:7890:a:bc::]\"')\n    end\n\n    test \"IPv6 with leading zeroes\" do\n      assert [{0x0000, 0x0001, 0x0002, 0x0003, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[0:01:002:0003:0000::]\"')\n      assert [{0x000A, 0x001A, 0x002A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[0a:01a:002a::]\"')\n      assert [{0x00AB, 0x01AB, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[0ab:01ab::]\"')\n      assert [{0x0ABC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Forwarded.parse(~S'for=\"[0abc::]\"')\n    end\n\n    test \"IPv6 with mixed case\" do\n      assert [{0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD}] == Forwarded.parse(~S'for=\"[abcd:abcD:abCd:abCD:aBcd:aBcD:aBCd:aBCD]\"')\n      assert [{0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD}] == Forwarded.parse(~S'for=\"[Abcd:AbcD:AbCd:AbCD:ABcd:ABcD:ABCd:ABCD]\"')\n    end\n\n    test \"semicolons\" do\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'for=1.2.3.4;proto=http;by=2.3.4.5')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'proto=http;for=1.2.3.4;by=2.3.4.5')\n      assert [{1, 2, 3, 4}] == Forwarded.parse(~S'proto=http;by=2.3.4.5;for=1.2.3.4')\n\n      assert [] == Forwarded.parse(~S'for=1.2.3.4proto=http;by=2.3.4.5')\n      assert [] == Forwarded.parse(~S'proto=httpfor=1.2.3.4;by=2.3.4.5')\n      assert [] == Forwarded.parse(~S'proto=http;by=2.3.4.5for=1.2.3.4')\n\n      assert [] == Forwarded.parse(~S'for=1.2.3.4;proto=http;by=2.3.4.5;')\n      assert [] == Forwarded.parse(~S'proto=http;for=1.2.3.4;by=2.3.4.5;')\n      assert [] == Forwarded.parse(~S'proto=http;by=2.3.4.5;for=1.2.3.4;')\n\n      assert [] == Forwarded.parse(~S'for=1.2.3.4;proto=http;for=2.3.4.5')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4;for=2.3.4.5;proto=http')\n      assert [] == Forwarded.parse(~S'proto=http;for=1.2.3.4;for=2.3.4.5')\n    end\n\n    test \"parameters other than `for`\" do\n      assert [] == Forwarded.parse(~S'by=1.2.3.4')\n      assert [] == Forwarded.parse(~S'host=example.com')\n      assert [] == Forwarded.parse(~S'proto=http')\n      assert [] == Forwarded.parse(~S'by=1.2.3.4;proto=http;host=example.com')\n    end\n\n    test \"bad whitespace\" do\n      assert [] == Forwarded.parse(~S'for= 1.2.3.4')\n      assert [] == Forwarded.parse(~S'for = 1.2.3.4')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4; proto=http')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4 ;proto=http')\n      assert [] == Forwarded.parse(~S'for=1.2.3.4 ; proto=http')\n      assert [] == Forwarded.parse(~S'proto=http; for=1.2.3.4')\n      assert [] == Forwarded.parse(~S'proto=http ;for=1.2.3.4')\n      assert [] == Forwarded.parse(~S'proto=http ; for=1.2.3.4')\n    end\n\n    test \"commas\" do\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(~S'for=1.2.3.4, for=2.3.4.5')\n      assert [{1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}] == Forwarded.parse(~S'for=1.2.3.4, for=\"[::2:3:4:5]\"')\n      assert [{1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}] == Forwarded.parse(~S'for=1.2.3.4, for=\"[::2:3:4:5]\"')\n      assert [{0, 0, 0, 0, 1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(~S'for=\"[::1:2:3:4]\", for=2.3.4.5')\n      assert [{0, 0, 0, 0, 1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}] == Forwarded.parse(~S'for=\"[::1:2:3:4]\", for=\"[::2:3:4:5]\"')\n    end\n\n    test \"optional whitespace\" do\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}, {5, 6, 7, 8}] == Forwarded.parse(\"for=1.2.3.4,for=2.3.4.5,\\sfor=3.4.5.6\\s,for=4.5.6.7\\s,\\sfor=5.6.7.8\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}, {5, 6, 7, 8}] == Forwarded.parse(\"for=1.2.3.4,for=2.3.4.5,\\tfor=3.4.5.6\\t,for=4.5.6.7\\t,\\tfor=5.6.7.8\")\n\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\s,\\s\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\s,\\s\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\s,\\t\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\s,\\t\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\t,\\s\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\t,\\s\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\t,\\t\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\s\\t,\\t\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\s,\\s\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\s,\\s\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\s,\\t\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\s,\\t\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\t,\\s\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\t,\\s\\tfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\t,\\t\\sfor=2.3.4.5\")\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\t,\\t\\tfor=2.3.4.5\")\n\n      assert [{1, 2, 3, 4}, {2, 3, 4, 5}] == Forwarded.parse(\"for=1.2.3.4\\t\\s\\s\\s\\s\\t\\s\\t\\s\\t,\\t\\s\\s\\t\\tfor=2.3.4.5\")\n    end\n\n    test \"commas and semicolons\" do\n      assert [{1, 2, 3, 4}, {0, 0, 0, 0, 2, 3, 4, 5}, {3, 4, 5, 6}, {0, 0, 0, 0, 4, 5, 6, 7}] == Forwarded.parse(~S'for=1.2.3.4, for=\"[::2:3:4:5]\";proto=http;host=example.com, proto=http;for=3.4.5.6;by=127.0.0.1, proto=http;host=example.com;for=\"[::4:5:6:7]\"')\n    end\n  end\nend\n"
  },
  {
    "path": "test/remote_ip/parsers/generic_test.exs",
    "content": "defmodule RemoteIp.Parsers.GenericTest do\n  use ExUnit.Case, async: true\n\n  alias RemoteIp.Parsers.Generic\n\n  doctest Generic\n\n  describe \"parsing\" do\n    test \"bad IPs\" do\n      assert [] == Generic.parse(\"\")\n      assert [] == Generic.parse(\"      \")\n      assert [] == Generic.parse(\"not_an_ip\")\n      assert [] == Generic.parse(\"unknown\")\n      assert [] == Generic.parse(<<240, 253, 253, 253>>)\n    end\n\n    test \"bad IPv4\" do\n      assert [] == Generic.parse(\"1\")\n      assert [] == Generic.parse(\"1.2\")\n      assert [] == Generic.parse(\"1.2.3\")\n      assert [] == Generic.parse(\"1000.2.3.4\")\n      assert [] == Generic.parse(\"1.2000.3.4\")\n      assert [] == Generic.parse(\"1.2.3000.4\")\n      assert [] == Generic.parse(\"1.2.3.4000\")\n      assert [] == Generic.parse(\"1abc.2.3.4\")\n      assert [] == Generic.parse(\"1.2abc.3.4\")\n      assert [] == Generic.parse(\"1.2.3.4abc\")\n      assert [] == Generic.parse(\"1.2.3abc.4\")\n      assert [] == Generic.parse(\"1.2.3.4abc\")\n      assert [] == Generic.parse(\"1.2.3.4.5\")\n    end\n\n    test \"bad IPv6\" do\n      assert [] == Generic.parse(\"1:\")\n      assert [] == Generic.parse(\"1:2\")\n      assert [] == Generic.parse(\"1:2:3\")\n      assert [] == Generic.parse(\"1:2:3:4\")\n      assert [] == Generic.parse(\"1:2:3:4:5\")\n      assert [] == Generic.parse(\"1:2:3:4:5:6\")\n      assert [] == Generic.parse(\"1:2:3:4:5:6:7\")\n      assert [] == Generic.parse(\"1:2:3:4:5:6:7:8:\")\n      assert [] == Generic.parse(\"1:2:3:4:5:6:7:8:9\")\n      assert [] == Generic.parse(\"1:::2:3:4:5:6:7:8\")\n      assert [] == Generic.parse(\"a:b:c:d:e:f::g\")\n    end\n\n    test \"IPv4\" do\n      assert [{1, 2, 3, 4}] == Generic.parse(\"1.2.3.4\")\n      assert [{1, 2, 3, 4}] == Generic.parse(\"   1.2.3.4   \")\n    end\n\n    test \"IPv6 without ::\" do\n      assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456:7890:a:bc:def:d34d\")\n      assert [{0x0001, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23:456:7890:a:bc:1.2.3.4\")\n    end\n\n    test \"IPv6 with :: at position 0\" do\n      assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"::23:456:7890:a:bc:def:d34d\")\n      assert [{0x0000, 0x0023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"::23:456:7890:a:bc:1.2.3.4\")\n      assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"::456:7890:a:bc:def:d34d\")\n      assert [{0x0000, 0x0000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"::456:7890:a:bc:1.2.3.4\")\n      assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"::7890:a:bc:def:d34d\")\n      assert [{0x0000, 0x0000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"::7890:a:bc:1.2.3.4\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"::a:bc:def:d34d\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"::a:bc:1.2.3.4\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"::bc:def:d34d\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"::bc:1.2.3.4\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Generic.parse(\"::def:d34d\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse(\"::1.2.3.4\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Generic.parse(\"::d34d\")\n      assert [{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"::\")\n    end\n\n    test \"IPv6 with :: at position 1\" do\n      assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1::456:7890:a:bc:def:d34d\")\n      assert [{0x0001, 0x000, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1::456:7890:a:bc:1.2.3.4\")\n      assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1::7890:a:bc:def:d34d\")\n      assert [{0x0001, 0x000, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1::7890:a:bc:1.2.3.4\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1::a:bc:def:d34d\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1::a:bc:1.2.3.4\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1::bc:def:d34d\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1::bc:1.2.3.4\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Generic.parse(\"1::def:d34d\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse(\"1::1.2.3.4\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Generic.parse(\"1::d34d\")\n      assert [{0x0001, 0x000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"1::\")\n    end\n\n    test \"IPv6 with :: at position 2\" do\n      assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23::7890:a:bc:def:d34d\")\n      assert [{0x0001, 0x023, 0x0000, 0x7890, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23::7890:a:bc:1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23::a:bc:def:d34d\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23::a:bc:1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23::bc:def:d34d\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23::bc:1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23::def:d34d\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse(\"1:23::1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Generic.parse(\"1:23::d34d\")\n      assert [{0x0001, 0x023, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"1:23::\")\n    end\n\n    test \"IPv6 with :: at position 3\" do\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000A, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456::a:bc:def:d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x000A, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23:456::a:bc:1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456::bc:def:d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23:456::bc:1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456::def:d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse(\"1:23:456::1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0xD34D}] == Generic.parse(\"1:23:456::d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"1:23:456::\")\n    end\n\n    test \"IPv6 with :: at position 4\" do\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00BC, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456:7890::bc:def:d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x00BC, 0x0102, 0x0304}] == Generic.parse(\"1:23:456:7890::bc:1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456:7890::def:d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0102, 0x0304}] == Generic.parse(\"1:23:456:7890::1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0xD34D}] == Generic.parse(\"1:23:456:7890::d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"1:23:456:7890::\")\n    end\n\n    test \"IPv6 with :: at position 5\" do\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0DEF, 0xD34D}] == Generic.parse(\"1:23:456:7890:a::def:d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0102, 0x0304}] == Generic.parse(\"1:23:456:7890:a::1.2.3.4\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0000, 0xD34D}] == Generic.parse(\"1:23:456:7890:a::d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"1:23:456:7890:a::\")\n    end\n\n    test \"IPv6 with :: at position 6\" do\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0000, 0xD34D}] == Generic.parse(\"1:23:456:7890:a:bc::d34d\")\n      assert [{0x0001, 0x023, 0x0456, 0x7890, 0x000A, 0x00BC, 0x0000, 0x0000}] == Generic.parse(\"1:23:456:7890:a:bc::\")\n    end\n\n    test \"IPv6 with leading zeroes\" do\n      assert [{0x0000, 0x0001, 0x0002, 0x0003, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"0:01:002:0003:0000::\")\n      assert [{0x000A, 0x001A, 0x002A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"0a:01a:002a::\")\n      assert [{0x00AB, 0x01AB, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"0ab:01ab::\")\n      assert [{0x0ABC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}] == Generic.parse(\"0abc::\")\n    end\n\n    test \"IPv6 with mixed case\" do\n      assert [{0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD}] == Generic.parse(\"abcd:abcD:abCd:abCD:aBcd:aBcD:aBCd:aBCD\")\n      assert [{0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD, 0xABCD}] == Generic.parse(\"Abcd:AbcD:AbCd:AbCD:ABcd:ABcD:ABCd:ABCD\")\n    end\n\n    test \"commas with optional whitespace\" do\n      assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse(\"127.0.0.1,::1\")\n      assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse(\"127.0.0.1,\\s::1\")\n      assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse(\"127.0.0.1\\s,::1\")\n      assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse(\"127.0.0.1\\s,\\s::1\")\n      assert [{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}] == Generic.parse(\"\\s\\t\\s\\t127.0.0.1\\t\\t\\s\\s,\\s\\t\\t\\s::1\\t\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/remote_ip_test.exs",
    "content": "defmodule RemoteIpTest do\n  use ExUnit.Case, async: true\n  use Plug.Test\n\n  doctest RemoteIp\n\n  @unknown [\n    {\"forwarded\", \"for=unknown\"},\n    {\"x-forwarded-for\", \"not_an_ip\"},\n    {\"x-client-ip\", \"_obf\"},\n    {\"x-real-ip\", \"1.2.3\"},\n    {\"custom\", \"::g\"}\n  ]\n\n  @loopback [\n    {\"forwarded\", \"for=127.0.0.1\"},\n    {\"x-forwarded-for\", \"::1\"},\n    {\"x-client-ip\", \"127.0.0.2\"},\n    {\"x-real-ip\", \"::::::1\"},\n    {\"custom\", \"127.127.127.127\"}\n  ]\n\n  @private [\n    {\"forwarded\", \"for=10.0.0.1\"},\n    {\"x-forwarded-for\", \"172.16.0.1\"},\n    {\"x-client-ip\", \"fd00::\"},\n    {\"x-real-ip\", \"192.168.10.10\"},\n    {\"custom\", \"172.31.41.59\"}\n  ]\n\n  @public_v4 [\n    {\"forwarded\", \"for=2.71.82.8\"},\n    {\"x-forwarded-for\", \"2.71.82.8\"},\n    {\"x-client-ip\", \"2.71.82.8\"},\n    {\"x-real-ip\", \"2.71.82.8\"},\n    {\"custom\", \"2.71.82.8\"}\n  ]\n\n  @public_v6 [\n    {\"forwarded\", \"for=\\\"[::2.71.82.8]\\\"\"},\n    {\"x-forwarded-for\", \"::247:5208\"},\n    {\"x-client-ip\", \"0:0:0:0:0:0:2.71.82.8\"},\n    {\"x-real-ip\", \"0::0:247:5208\"},\n    {\"custom\", \"0:0::2.71.82.8\"}\n  ]\n\n  def call(conn, opts \\\\ []) do\n    RemoteIp.call(conn, RemoteIp.init(opts))\n  end\n\n  describe \"call/2\" do\n    test \"no headers\" do\n      peer = {86, 75, 30, 9}\n      head = []\n      conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n      assert call(conn).remote_ip == peer\n      assert Logger.metadata()[:remote_ip] == \"86.75.30.9\"\n    end\n\n    test \"invalid ip address\" do\n      peer = \"an invalid ip\"\n      head = []\n      conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n      assert call(conn).remote_ip == peer\n      assert Logger.metadata()[:remote_ip] == nil\n    end\n\n    for {header, value} <- @unknown do\n      test \"#{header} header from unknown IP\" do\n        peer = {1, 2, 3, 4}\n        head = [{unquote(header), unquote(value)}]\n        conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n        opts = [headers: [unquote(header)]]\n        assert call(conn, opts).remote_ip == peer\n        assert Logger.metadata()[:remote_ip] == \"1.2.3.4\"\n      end\n    end\n\n    for {header, value} <- @loopback do\n      test \"#{header} header from loopback IP\" do\n        peer = {0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF}\n        head = [{unquote(header), unquote(value)}]\n        conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n        opts = [headers: [unquote(header)]]\n        assert call(conn, opts).remote_ip == peer\n        assert Logger.metadata()[:remote_ip] == \"d:e:a:d:b:e:e:f\"\n      end\n    end\n\n    for {header, value} <- @private do\n      test \"#{header} header from private IP\" do\n        peer = {0xDE, 0xAD, 0, 0, 0, 0, 0xBE, 0xEF}\n        head = [{unquote(header), unquote(value)}]\n        conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n        opts = [headers: [unquote(header)]]\n        assert call(conn, opts).remote_ip == peer\n        assert Logger.metadata()[:remote_ip] == \"de:ad::be:ef\"\n      end\n    end\n\n    for {header, value} <- @public_v4 do\n      test \"#{header} header from public IP (v4)\" do\n        peer = {3, 141, 59, 27}\n        head = [{unquote(header), unquote(value)}]\n        conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n        opts = [headers: [unquote(header)]]\n        assert call(conn, opts).remote_ip == {2, 71, 82, 8}\n        assert Logger.metadata()[:remote_ip] == \"2.71.82.8\"\n      end\n    end\n\n    for {header, value} <- @public_v6 do\n      test \"#{header} header from public IP (v6)\" do\n        peer = {3, 141, 59, 27}\n        head = [{unquote(header), unquote(value)}]\n        conn = %Plug.Conn{remote_ip: peer, req_headers: head}\n        opts = [headers: [unquote(header)]]\n        assert call(conn, opts).remote_ip == {0, 0, 0, 0, 0, 0, 583, 21000}\n        assert Logger.metadata()[:remote_ip] == \"::2.71.82.8\"\n      end\n    end\n  end\n\n  describe \"from/2\" do\n    test \"no headers\" do\n      head = []\n      assert RemoteIp.from(head) == nil\n      assert Logger.metadata()[:remote_ip] == nil\n    end\n\n    for {header, value} <- @unknown do\n      test \"#{header} header from unknown IP\" do\n        head = [{unquote(header), unquote(value)}]\n        opts = [headers: [unquote(header)]]\n        assert RemoteIp.from(head, opts) == nil\n        assert Logger.metadata()[:remote_ip] == nil\n      end\n    end\n\n    for {header, value} <- @loopback do\n      test \"#{header} header from loopback IP\" do\n        head = [{unquote(header), unquote(value)}]\n        opts = [headers: [unquote(header)]]\n        assert RemoteIp.from(head, opts) == nil\n        assert Logger.metadata()[:remote_ip] == nil\n      end\n    end\n\n    for {header, value} <- @private do\n      test \"#{header} header from private IP\" do\n        head = [{unquote(header), unquote(value)}]\n        opts = [headers: [unquote(header)]]\n        assert RemoteIp.from(head, opts) == nil\n        assert Logger.metadata()[:remote_ip] == nil\n      end\n    end\n\n    for {header, value} <- @public_v4 do\n      test \"#{header} header from public IP (v4)\" do\n        head = [{unquote(header), unquote(value)}]\n        opts = [headers: [unquote(header)]]\n        assert RemoteIp.from(head, opts) == {2, 71, 82, 8}\n        assert Logger.metadata()[:remote_ip] == nil\n      end\n    end\n\n    for {header, value} <- @public_v6 do\n      test \"#{header} header from public IP (v6)\" do\n        head = [{unquote(header), unquote(value)}]\n        opts = [headers: [unquote(header)]]\n        assert RemoteIp.from(head, opts) == {0, 0, 0, 0, 0, 0, 583, 21000}\n        assert Logger.metadata()[:remote_ip] == nil\n      end\n    end\n  end\n\n  @proxies [\n    {\"forwarded\", \"for=1.2.3.4\"},\n    {\"x-forwarded-for\", \"::a\"},\n    {\"x-client-ip\", \"1:2:3:4:5:6:7:8\"},\n    {\"x-real-ip\", \"4.4.4.4\"}\n  ]\n\n  describe \":proxies option\" do\n    test \"can block presumed clients\" do\n      head = @proxies\n      opts = [proxies: ~w[1.2.0.0/16 ::a/128 4.0.0.0/8 1::/30]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"cannot block known clients\" do\n      head = @proxies\n      opts = [proxies: ~w[0.0.0.0/0 ::/0], clients: ~w[1.2.0.0/16]]\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n    end\n\n    test \"always includes reserved IPs\" do\n      head = @proxies ++ @loopback ++ @private\n      opts = [proxies: ~w[1.2.0.0/16 ::a/128 4.0.0.0/8 1::/30 8.8.8.8/32]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"can be an MFA\" do\n      head = [{\"x-forwarded-for\", \"1.2.3.4, 2.3.4.5\"}]\n      opts = [proxies: {Application, :get_env, [:remote_ip_test, :proxies]}]\n\n      Application.put_env(:remote_ip_test, :proxies, [])\n      assert RemoteIp.from(head, opts) == {2, 3, 4, 5}\n\n      Application.put_env(:remote_ip_test, :proxies, ~w[2.0.0.0/8])\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n    end\n  end\n\n  @clients [\n    {\"forwarded\", \"for=2.71.82.81\"},\n    {\"x-forwarded-for\", \"82.84.59.0\"},\n    {\"x-client-ip\", \"45.235.36.0\"},\n    {\"x-real-ip\", \"28.74.71.35\"}\n  ]\n\n  describe \":clients option\" do\n    test \"can allow reserved IPs\" do\n      head = @loopback ++ @private\n      opts = [clients: ~w[192.168.10.0/24]]\n      assert RemoteIp.from(head, opts) == {192, 168, 10, 10}\n    end\n\n    test \"can allow known proxies\" do\n      head = @clients\n\n      opts = [\n        proxies: ~w[2.0.0.0/8 82.84.0.0/16 45.235.36.0/24 28.74.71.35/32],\n        clients: ~w[2.71.0.0/16]\n      ]\n\n      assert RemoteIp.from(head, opts) == {2, 71, 82, 81}\n    end\n\n    test \"doesn't impact presumed clients\" do\n      head = @clients\n      opts = [clients: ~w[2.0.0.0/8 82.84.0.0/16 45.235.36.0/24 28.74.71.35/32]]\n      assert RemoteIp.from(head, opts) == {28, 74, 71, 35}\n    end\n\n    test \"can be an MFA\" do\n      head = [{\"x-forwarded-for\", \"1.2.3.4, 127.0.0.1\"}]\n      opts = [clients: {Application, :get_env, [:remote_ip_test, :clients]}]\n\n      Application.put_env(:remote_ip_test, :clients, [])\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n\n      Application.put_env(:remote_ip_test, :clients, ~w[127.0.0.0/8])\n      assert RemoteIp.from(head, opts) == {127, 0, 0, 1}\n    end\n  end\n\n  @headers [\n    {\"forwarded\", \"for=1.2.3.4\"},\n    {\"x-forwarded-for\", \"1.2.3.4\"},\n    {\"x-client-ip\", \"1.2.3.4\"},\n    {\"x-real-ip\", \"1.2.3.4\"}\n  ]\n\n  describe \":headers option\" do\n    test \"specifies which headers to use\" do\n      head = [{\"a\", \"1.2.3.4\"}, {\"b\", \"2.3.4.5\"}, {\"c\", \"3.4.5.6\"}]\n\n      assert RemoteIp.from(head, headers: ~w[a b]) == {2, 3, 4, 5}\n      assert RemoteIp.from(head, headers: ~w[a c]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[b a]) == {2, 3, 4, 5}\n      assert RemoteIp.from(head, headers: ~w[b c]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[c a]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[c b]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[a]) == {1, 2, 3, 4}\n      assert RemoteIp.from(head, headers: ~w[b]) == {2, 3, 4, 5}\n      assert RemoteIp.from(head, headers: ~w[c]) == {3, 4, 5, 6}\n    end\n\n    for {header, value} <- @headers do\n      test \"includes #{header} by default\" do\n        head = [{unquote(header), unquote(value)}]\n        assert RemoteIp.from(head) == {1, 2, 3, 4}\n      end\n    end\n\n    test \"overrides the defaults when specified\" do\n      head = @headers\n      opts = [headers: ~w[custom]]\n      fail = \"default headers are still being parsed\"\n      refute RemoteIp.from(head, opts) == {1, 2, 3, 4}, fail\n    end\n\n    test \"doesn't care about order\" do\n      head = [{\"a\", \"1.2.3.4\"}, {\"b\", \"2.3.4.5\"}, {\"c\", \"3.4.5.6\"}]\n\n      assert RemoteIp.from(head, headers: ~w[a b c]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[a c b]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[b a c]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[b c a]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[c a b]) == {3, 4, 5, 6}\n      assert RemoteIp.from(head, headers: ~w[c b a]) == {3, 4, 5, 6}\n    end\n\n    test \"can be an MFA\" do\n      head = [{\"a\", \"1.2.3.4\"}, {\"b\", \"2.3.4.5\"}]\n      opts = [headers: {Application, :get_env, [:remote_ip_test, :headers]}]\n\n      Application.put_env(:remote_ip_test, :headers, ~w[a])\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n\n      Application.put_env(:remote_ip_test, :headers, ~w[b])\n      assert RemoteIp.from(head, opts) == {2, 3, 4, 5}\n    end\n  end\n\n  describe \"multiple headers\" do\n    test \"from unknown to unknown\" do\n      head = [{\"forwarded\", \"for=unknown,for=_obf\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from unknown to loopback\" do\n      head = [{\"x-forwarded-for\", \"unknown,::1\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from unknown to private\" do\n      head = [{\"x-client-ip\", \"_obf, fc00:ABCD\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from unknown to proxy\" do\n      head = [{\"x-real-ip\", \"not_an_ip , 1.2.3.4\"}]\n      opts = [proxies: ~w[1.0.0.0/12]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from unknown to client\" do\n      head = [{\"custom\", \"unknown ,1.2.3.4\"}]\n      opts = [headers: ~w[custom]]\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n    end\n\n    test \"from loopback to unknown\" do\n      head = [{\"forwarded\", \"for=\\\"[::1]\\\"\"}, {\"x-forwarded-for\", \"_bogus\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from loopback to loopback\" do\n      head = [{\"x-client-ip\", \"127.0.0.1\"}, {\"x-real-ip\", \"127.0.0.1\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from loopback to private\" do\n      head = [{\"custom\", \"127.0.0.10\"}, {\"forwarded\", \"for=\\\"[fc00::1]\\\"\"}]\n      opts = [headers: ~w[forwarded custom]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from loopback to proxy\" do\n      head = [{\"forwarded\", \"for=127.0.0.1\"}, {\"forwarded\", \"for=1.2.3.4\"}]\n      opts = [proxies: ~w[1.2.3.4/32]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from loopback to client\" do\n      head = [{\"x-forwarded-for\", \"127.0.0.1\"}, {\"x-forwarded-for\", \"1.2.3.4\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n    end\n\n    test \"from private to unknown\" do\n      head = [{\"x-client-ip\", \"fc00::ABCD\"}, {\"x-client-ip\", \"_obf\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from private to loopback\" do\n      head = [{\"x-real-ip\", \"192.168.1.2\"}, {\"x-real-ip\", \"::1\"}]\n      opts = []\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from private to private\" do\n      head = [{\"custom\", \"10.0.0.1\"}, {\"custom\", \"10.0.0.2\"}]\n      opts = [headers: ~w[custom]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from private to proxy\" do\n      head = [{\"forwarded\", \"for=10.0.10.0, for=\\\"[::1.2.3.4]\\\"\"}]\n      opts = [proxies: ~w[::/64]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from private to client\" do\n      head = [{\"x-forwarded-for\", \"10.0.10.0, ::1.2.3.4\"}]\n      opts = [proxies: ~w[255.0.0.0/8]]\n      assert RemoteIp.from(head, opts) == {0, 0, 0, 0, 0, 0, 258, 772}\n    end\n\n    test \"from proxy to unknown\" do\n      head = [{\"x-client-ip\", \"a:b:c:d:e:f::,unknown\"}]\n      opts = [proxies: ~w[::/0]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from proxy to loopback\" do\n      head = [\n        {\"x-real-ip\", \"2001:0db8:85a3:0000:0000:8A2E:0370:7334\"},\n        {\"x-real-ip\", \"127.0.0.2\"}\n      ]\n\n      opts = [proxies: ~w[2001:0db8:85a3::8A2E:0370:7334/128]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from proxy to private\" do\n      head = [{\"custom\", \"3.4.5.6 , 172.16.1.2\"}]\n      opts = [headers: ~w[custom], proxies: ~w[3.0.0.0/8]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from proxy to proxy\" do\n      head = [{\"forwarded\", \"for=1.2.3.4, for=1.2.3.5\"}]\n      opts = [proxies: ~w[1.2.3.0/24]]\n      assert RemoteIp.from(head, opts) == nil\n    end\n\n    test \"from proxy to client\" do\n      head = [{\"x-forwarded-for\", \"::1:2:3:4, ::3:4:5:6\"}]\n      opts = [proxies: ~w[::1:2:3:4/128]]\n      assert RemoteIp.from(head, opts) == {0, 0, 0, 0, 3, 4, 5, 6}\n    end\n\n    test \"from client to unknown\" do\n      head = [{\"x-client-ip\", \"a:b:c:d:e:f::,unknown\"}]\n      opts = [proxies: ~w[b::/64]]\n      assert RemoteIp.from(head, opts) == {10, 11, 12, 13, 14, 15, 0, 0}\n    end\n\n    test \"from client to loopback\" do\n      head = [{\"x-real-ip\", \"127.0.0.1\"}, {\"x-real-ip\", \"127.0.0.2\"}]\n      opts = [clients: ~w[127.0.0.1/32]]\n      assert RemoteIp.from(head, opts) == {127, 0, 0, 1}\n    end\n\n    test \"from client to private\" do\n      head = [{\"custom\", \"::1.2.3.4, 10.0.10.0\"}]\n      opts = [proxies: ~w[1:2:3:4::/64], headers: ~w[custom]]\n      assert RemoteIp.from(head, opts) == {0, 0, 0, 0, 0, 0, 258, 772}\n    end\n\n    test \"from client to proxy\" do\n      head = [{\"forwarded\", \"for=1.2.3.4,for=3.4.5.6\"}]\n      opts = [proxies: ~w[3.4.5.0/24]]\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n    end\n\n    test \"from client to client\" do\n      head = [{\"x-forwarded-for\", \"1.2.3.4\"}, {\"x-forwarded-for\", \"10.45.0.1\"}]\n      opts = [clients: ~w[10.45.0.0/16]]\n      assert RemoteIp.from(head, opts) == {10, 45, 0, 1}\n    end\n\n    test \"more than two hops\" do\n      head = [\n        {\"forwarded\", \"for=\\\"[fe80::0202:b3ff:fe1e:8329]\\\"\"},\n        {\"forwarded\", \"for=1.2.3.4\"},\n        {\"x-forwarded-for\", \"172.16.0.10\"},\n        {\"x-client-ip\", \"::1, ::1\"},\n        {\"x-real-ip\", \"2.3.4.5, fc00::1, 2.4.6.8\"}\n      ]\n\n      opts = [proxies: ~w[2.0.0.0/8]]\n      assert RemoteIp.from(head, opts) == {1, 2, 3, 4}\n    end\n  end\n\n  defmodule ParserA do\n    @behaviour RemoteIp.Parser\n    @impl RemoteIp.Parser\n    def parse(value) do\n      ips = RemoteIp.Parsers.Generic.parse(value)\n      ips |> Enum.map(fn {a, b, c, d} -> {10 + a, 10 + b, 10 + c, 10 + d} end)\n    end\n  end\n\n  defmodule ParserB do\n    @behaviour RemoteIp.Parser\n    @impl RemoteIp.Parser\n    def parse(value) do\n      ips = RemoteIp.Parsers.Generic.parse(value)\n      ips |> Enum.map(fn {a, b, c, d} -> {20 + a, 20 + b, 20 + c, 20 + d} end)\n    end\n  end\n\n  defmodule ParserC do\n    @behaviour RemoteIp.Parser\n    @impl RemoteIp.Parser\n    def parse(value) do\n      ips = RemoteIp.Parsers.Generic.parse(value)\n      ips |> Enum.map(fn {a, b, c, d} -> {30 + a, 30 + b, 30 + c, 30 + d} end)\n    end\n  end\n\n  describe \":parsers option\" do\n    test \"can customize parsers for specific headers\" do\n      headers = [{\"a\", \"1.2.3.4\"}, {\"b\", \"2.3.4.5\"}, {\"c\", \"3.4.5.6\"}]\n      parsers = %{\"a\" => ParserA, \"b\" => ParserB, \"c\" => ParserC}\n\n      assert RemoteIp.from(headers, parsers: parsers, headers: ~w[a]) == {11, 12, 13, 14}\n      assert RemoteIp.from(headers, parsers: parsers, headers: ~w[b]) == {22, 23, 24, 25}\n      assert RemoteIp.from(headers, parsers: parsers, headers: ~w[c]) == {33, 34, 35, 36}\n    end\n\n    test \"doesn't clobber generic parser on other headers\" do\n      headers = [{\"a\", \"1.2.3.4\"}, {\"b\", \"2.3.4.5\"}, {\"c\", \"3.4.5.6\"}]\n      parsers = %{\"a\" => ParserA, \"c\" => ParserC}\n\n      assert RemoteIp.from(headers, parsers: parsers, headers: ~w[a]) == {11, 12, 13, 14}\n      assert RemoteIp.from(headers, parsers: parsers, headers: ~w[b]) == {2, 3, 4, 5}\n      assert RemoteIp.from(headers, parsers: parsers, headers: ~w[c]) == {33, 34, 35, 36}\n    end\n\n    test \"doesn't clobber Forwarded parser by default\" do\n      headers = [{\"forwarded\", \"for=1.2.3.4\"}]\n      parsers = %{\"a\" => ParserA, \"b\" => ParserB, \"c\" => ParserC}\n      options = [parsers: parsers, headers: ~w[forwarded a b c]]\n      assert RemoteIp.from(headers, options) == {1, 2, 3, 4}\n    end\n\n    test \"can clobber Forwarded parser\" do\n      headers = [{\"forwarded\", \"1.2.3.4\"}]\n      parsers = %{\"forwarded\" => ParserA}\n      options = [parsers: parsers, headers: ~w[forwarded]]\n      assert RemoteIp.from(headers, options) == {11, 12, 13, 14}\n    end\n\n    test \"can be an MFA\" do\n      headers = [{\"x\", \"1.2.3.4\"}]\n      parsers = {Application, :get_env, [:remote_ip_test, :parsers]}\n      options = [parsers: parsers, headers: ~w[x]]\n\n      Application.put_env(:remote_ip_test, :parsers, %{\"x\" => ParserA})\n      assert RemoteIp.from(headers, options) == {11, 12, 13, 14}\n\n      Application.put_env(:remote_ip_test, :parsers, %{\"x\" => ParserC})\n      assert RemoteIp.from(headers, options) == {31, 32, 33, 34}\n    end\n  end\n\n  defmodule App do\n    use Plug.Router\n\n    plug RemoteIp,\n      parsers: {__MODULE__, :parsers, []},\n      headers: {__MODULE__, :config, [\"HEADERS\"]},\n      proxies: {__MODULE__, :config, [\"PROXIES\"]},\n      clients: {__MODULE__, :config, [\"CLIENTS\"]}\n\n    plug :match\n    plug :dispatch\n\n    get \"/ip\" do\n      send_resp(conn, 200, :inet.ntoa(conn.remote_ip))\n    end\n\n    def config(var) do\n      System.get_env() |> Map.get(var, \"\") |> String.split(\",\", trim: true)\n    end\n\n    def parsers do\n      Enum.into(config(\"PARSERS\"), %{}, fn spec ->\n        [header, parser] = String.split(spec, \":\")\n        {header, :\"Elixir.RemoteIpTest.#{parser}\"}\n      end)\n    end\n  end\n\n  test \"runtime configuration\" do\n    try do\n      conn = conn(:get, \"/ip\")\n      conn = conn |> put_req_header(\"a\", \"1.2.3.4, 192.168.0.1, 2.3.4.5\")\n      conn = conn |> put_req_header(\"b\", \"3.4.5.6, 192.168.0.1, 4.5.6.7\")\n\n      assert App.call(conn, App.init([])).resp_body == \"127.0.0.1\"\n\n      System.put_env(\"HEADERS\", \"a\")\n      assert App.call(conn, App.init([])).resp_body == \"2.3.4.5\"\n\n      System.put_env(\"PARSERS\", \"a:ParserA\")\n      assert App.call(conn, App.init([])).resp_body == \"12.13.14.15\"\n\n      System.put_env(\"PARSERS\", \"a:ParserB,c:ParserC\")\n      assert App.call(conn, App.init([])).resp_body == \"22.23.24.25\"\n\n      System.put_env(\"PROXIES\", \"22.0.0.0/8,212.188.0.0/16\")\n      assert App.call(conn, App.init([])).resp_body == \"21.22.23.24\"\n\n      System.delete_env(\"PARSERS\")\n\n      System.put_env(\"PROXIES\", \"2.1.0.0/16,2.2.0.0/16,2.3.0.0/16\")\n      assert App.call(conn, App.init([])).resp_body == \"1.2.3.4\"\n\n      System.put_env(\"CLIENTS\", \"192.0.0.0/8,1.2.3.4/32\")\n      assert App.call(conn, App.init([])).resp_body == \"192.168.0.1\"\n\n      System.put_env(\"HEADERS\", \"b,c,d\")\n      assert App.call(conn, App.init([])).resp_body == \"4.5.6.7\"\n\n      System.put_env(\"PROXIES\", \"4.5.6.0/24\")\n      assert App.call(conn, App.init([])).resp_body == \"192.168.0.1\"\n\n      System.put_env(\"CLIENTS\", \"4.5.7.0/24\")\n      assert App.call(conn, App.init([])).resp_body == \"3.4.5.6\"\n    after\n      System.delete_env(\"PARSERS\")\n      System.delete_env(\"HEADERS\")\n      System.delete_env(\"PROXIES\")\n      System.delete_env(\"CLIENTS\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "ExUnit.start(capture_log: true)\n"
  }
]