Freika/dawarich · warning · UrlValidatable::BlockedUrlError

Invalid URL format

Error message

Invalid URL format

What it means

Raised as BlockedUrlError when URI.parse raises URI::InvalidURIError while parsing the integration URL. Ruby's URI parser is strict: spaces, unescaped braces/brackets/pipes, non-ASCII characters, or control bytes make it throw. This catches malformed input before scheme/host checks run.

Source

Thrown at app/services/concerns/url_validatable.rb:80

    uri = URI.parse(url)
    unless %w[http https].include?(uri.scheme)
      raise BlockedUrlError, I18n.t('services.concerns.url_validatable.invalid_scheme', scheme: uri.scheme)
    end
    raise BlockedUrlError, I18n.t('services.concerns.url_validatable.host_required') if uri.host.blank?

    # Cloud refuses URLs that embed credentials. Self-hosters legitimately
    # use http://user:pass@host — homelab Immich behind nginx basic-auth
    # is a real config we don't want to break.
    if uri.userinfo.present? && !DawarichSettings.self_hosted?
      raise BlockedUrlError, I18n.t('services.concerns.url_validatable.embedded_credentials')
    end

    ip = IPAddr.new(Resolv.getaddress(uri.host))
    if blocked_ranges.any? { |range| range.include?(ip) }
      raise BlockedUrlError, I18n.t('services.concerns.url_validatable.blocked_address')
    end
  rescue URI::InvalidURIError
    raise BlockedUrlError, I18n.t('services.concerns.url_validatable.invalid_format')
  rescue Resolv::ResolvError
    raise BlockedUrlError, I18n.t('services.concerns.url_validatable.unresolvable_host', host: uri.host)
  end

  def blocked_ranges
    if DawarichSettings.self_hosted?
      ALWAYS_BLOCKED_RANGES
    else
      ALWAYS_BLOCKED_RANGES + CLOUD_ONLY_BLOCKED_RANGES
    end
  end
end

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Re-enter or sanitize the URL: strip whitespace/control characters and percent-encode spaces and unsafe characters (Addressable::URI.normalize or ERB::Util.url_encode for parts).
  2. Paste into a browser address bar first — if the browser also struggles or rewrites it, the URL itself is malformed.
  3. Check for invisible characters: url.each_char.select { |c| c.ord > 126 } in a console.
  4. On the client, trim and validate with a URL parser before submit.

Example fix

# before
url = "https://immich.example.com/api key=1" # space -> URI::InvalidURIError -> BlockedUrlError

# after
require 'addressable/uri'
url = Addressable::URI.normalize_component("https://immich.example.com/api key=1")
# => "https://immich.example.com/api%20key=1"
Defensive patterns

Strategy: validation

Validate before calling

require 'addressable/uri'
normalized = Addressable::URI.parse(url.to_s).normalize.to_s # raises Addressable::URI::InvalidURIError on garbage

Type guard

def parseable_url?(s)
  URI.parse(s.to_s)
  true
rescue URI::InvalidURIError, URI::BadURIError
  false
end

Try / catch

begin
  validate_integration_url!(url)
rescue BlockedUrlError => e
  errors.add(:url, 'URL is malformed - check for spaces and special characters')
end

Prevention

When it happens

Trigger: URLs containing literal spaces ('https://my immich.com'), unencoded query strings ('https://h/api?key=a b&x=[1]'), copied unicode (full-width colon, smart quotes), trailing garbage like 'https://host.com/\u{200b}' (zero-width space), or 'https://host#frag ment'.

Common situations: Copy-paste from chat apps or docs introducing smart quotes/zero-width chars, users typing URLs with spaces, values round-tripped through JSON with encoding damage, URLs with IPv6 literals that need brackets ('http://[::1]:2283' is fine but 'http://::1:2283' throws).

Related errors


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/efd5926605cea6e2. Report an issue: GitHub.