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
- 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).
- Paste into a browser address bar first — if the browser also struggles or rewrites it, the URL itself is malformed.
- Check for invisible characters: url.each_char.select { |c| c.ord > 126 } in a console.
- 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
- Trim and normalize pasted URLs (Addressable::URI.normalize_component) before storage.
- Reject URLs containing spaces or control characters client-side.
- Beware smart quotes and zero-width characters from chat/doc copy-paste.
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
- Invalid URL scheme: %{scheme}
- URL must include a host
- URL must not embed credentials (user:pass@host)
- URL resolves to a blocked address
- Could not resolve hostname: %{host}
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/efd5926605cea6e2.
Report an issue: GitHub.