Skip to content

Better URL validation#21

Merged
jaimeiniesta merged 2 commits into
masterfrom
better-url-validation
May 4, 2026
Merged

Better URL validation#21
jaimeiniesta merged 2 commits into
masterfrom
better-url-validation

Conversation

@jaimeiniesta

Copy link
Copy Markdown
Owner

The hand-written regex had a string of subtle defects (#14, #15) caused by greedy backtracking and a permissive trailing .*. Replace it with a small composition of standard-library primitives:

  • URI.parse/1 handles RFC parsing, port extraction, IDN-tolerant hosts
  • a precheck rejects whitespace and control chars
  • :inet.parse_address/1 recognizes IPv4/IPv6 hosts
  • the IANA TLD list, bundled at priv/tlds-alpha-by-domain.txt and loaded at compile time via @external_resource, validates the last domain label

Behavioral changes (intentional):

  • accept localhost, IP literals, and IPv6
  • accept any TLD on the IANA list (including museum, travel, etc.)
  • accept userinfo (user:pass@)
  • reject single-label hosts other than localhost
  • reject TLDs that are not on the IANA list
  • reject input containing whitespace or control chars

The integration test that previously walked every IANA TLD per request becomes a drift check: it asserts the bundled list still matches the live one. To refresh the list:

curl -sS https://data.iana.org/TLD/tlds-alpha-by-domain.txt
-o priv/tlds-alpha-by-domain.txt

The test suite is reorganized by URL component (schemes, hosts, ports, paths, query strings, fragments, userinfo, type guards, malformed input) with regression cases for #14 and #15 preserved.

The hand-written regex had a string of subtle defects (#14, #15) caused
by greedy backtracking and a permissive trailing `.*`. Replace it with a
small composition of standard-library primitives:

  * URI.parse/1 handles RFC parsing, port extraction, IDN-tolerant hosts
  * a precheck rejects whitespace and control chars
  * :inet.parse_address/1 recognizes IPv4/IPv6 hosts
  * the IANA TLD list, bundled at priv/tlds-alpha-by-domain.txt and loaded
    at compile time via @external_resource, validates the last domain label

Behavioral changes (intentional):

  * accept localhost, IP literals, and IPv6
  * accept any TLD on the IANA list (including museum, travel, etc.)
  * accept userinfo (user:pass@)
  * reject single-label hosts other than localhost
  * reject TLDs that are not on the IANA list
  * reject input containing whitespace or control chars

The integration test that previously walked every IANA TLD per request
becomes a drift check: it asserts the bundled list still matches the live
one. To refresh the list:

  curl -sS https://data.iana.org/TLD/tlds-alpha-by-domain.txt \
    -o priv/tlds-alpha-by-domain.txt

The test suite is reorganized by URL component (schemes, hosts, ports,
paths, query strings, fragments, userinfo, type guards, malformed input)
with regression cases for #14 and #15 preserved.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the previous hand-written URL validation regex with a more robust approach based on URI.parse/1, IP parsing, and validating the final domain label against a bundled IANA TLD list.

Changes:

  • Implemented valid_url?/1 using URI.parse/1, a whitespace/control-char precheck, :inet.parse_address/1 for IPs, and compile-time loading of an IANA TLD MapSet.
  • Reorganized valid_url?/1 tests by URL component and added/retained regression cases (including backtracking-related cases).
  • Added priv/tlds-alpha-by-domain.txt to the repo and an integration drift-check test comparing it to IANA’s live list.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
lib/funkspector/utils.ex Replaces regex-based URL validation with URI.parse/1 + host/TLD validation using a bundled IANA list.
test/utils_test.exs Reorganizes and expands URL validation tests; adds an integration drift check for the bundled TLD list.
priv/tlds-alpha-by-domain.txt Adds the IANA TLD snapshot used for compile-time validation and drift testing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/funkspector/utils.ex
Comment on lines +10 to +18
@tlds_file Path.join([:code.priv_dir(:funkspector), "tlds-alpha-by-domain.txt"])
@external_resource @tlds_file

@tlds @tlds_file
|> File.read!()
|> String.split("\n", trim: true)
|> Enum.reject(&String.starts_with?(&1, "#"))
|> Enum.map(&String.downcase/1)
|> MapSet.new()
Comment thread lib/funkspector/utils.ex Outdated
Comment thread test/utils_test.exs
Comment thread test/utils_test.exs
Comment on lines +318 to +324
bundled =
"priv/tlds-alpha-by-domain.txt"
|> File.read!()
|> String.split("\n", trim: true)
|> Enum.reject(&String.starts_with?(&1, "#"))
|> Enum.map(&String.downcase/1)
|> MapSet.new()
@jaimeiniesta
jaimeiniesta merged commit 9435bee into master May 4, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants