carrierwaveuploader/carrierwave · error · CarrierWave::DownloadError
couldn't parse URL: #{source}
Error message
couldn't parse URL: #{source} What it means
Raised as CarrierWave::DownloadError by CarrierWave::Downloader::Base#process_uri when the source string cannot be parsed as a URI. process_uri first parses with Addressable::URI, normalizes/encodes the path, query and fragment, then re-parses with URI.parse; if either parse raises URI::InvalidURIError or Addressable::URI::InvalidURIError, the source is rejected.
Source
Thrown at lib/carrierwave/downloader/base.rb:75
end
##
# Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
#
# === Parameters
#
# [url (String)] The URL where the remote file is stored
#
def process_uri(source)
uri = Addressable::URI.parse(source)
uri.host = uri.normalized_host
# Perform decode first, as the path is likely to be already encoded
uri.path = encode_path(decode_uri(uri.path)) if uri.path =~ CarrierWave::Utilities::Uri::PATH_UNSAFE
uri.query = encode_non_ascii(uri.query) if uri.query
uri.fragment = encode_non_ascii(uri.fragment) if uri.fragment
URI.parse(uri.to_s)
rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
raise CarrierWave::DownloadError, "couldn't parse URL: #{source}"
end
##
# If this returns true, SSRF protection will be bypassed.
# You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
#
# === Parameters
#
# [uri (URI)] The URI where the remote file is stored
#
# === Examples
#
# class CarrierWave::Downloader::CustomDownloader < CarrierWave::Downloader::Base
# def skip_ssrf_protection?(uri)
# uri.hostname == 'localhost' && uri.port == 80
# end
# end
#View on GitHub (pinned to b5f0abe10e)
Solutions
- Normalize/encode the URL before passing it: Addressable::URI.normalize(source).to_s
- Validate the param parses as a URI (and scheme is http/https) before assigning it to the uploader
- Strip whitespace from user input before download
- Rescue CarrierWave::DownloadError and feed the error back as a form validation message
Example fix
# before
uploader.download! 'http://example.com/photos/my dog.jpg' # DownloadError: couldn't parse URL
# after
require "addressable/uri"
uploader.download! Addressable::URI.normalize('http://example.com/photos/my dog.jpg').to_s Defensive patterns
Strategy: validation
Validate before calling
require 'addressable/uri' def safe_remote_url?(source) uri = Addressable::URI.parse(source.to_s.strip) uri.is_a?(Addressable::URI) && %w[http https].include?(uri.normalized_scheme) && !uri.normalized_host.nil? rescue Addressable::URI::InvalidURIError, URI::InvalidURIError, ArgumentError false end params[:avatar_url] = Addressable::URI.normalize(params[:avatar_url].to_s.strip).to_s if safe_remote_url?(params[:avatar_url])
Try / catch
begin uploader.download!(url) rescue CarrierWave::DownloadError => e errors.add(:url, :invalid_url) if e.message =~ /couldn't parse URL/ end
Prevention
- Normalize every user-supplied URL through Addressable::URI.normalize before it reaches the uploader
- Reject anything whose scheme is not http/https at the form-validation layer
- Strip whitespace from pasted URLs; browsers routinely hand you strings with spaces
When it happens
Trigger: Calling uploader.download! or assigning a remote URL whose string is not a valid URI: unencoded spaces in the path ('http://example.com/my file.jpg'), stray characters or incomplete URLs ('not a url', 'http://'), or percent-encoding that Addressable normalizes into something URI.parse still rejects.
Common situations: User-submitted URL fields pasted from browsers (unencoded unicode/space paths), string interpolation building URLs without escaping, or trimming code that leaves a trailing fragment the parser cannot handle.
Related errors
- could not download file: #{e.message}
- could not download file: No Content
- errors.messages.content_type_allowlist_error
- errors.messages.content_type_denylist_error
- errors.messages.min_width_error
AI-assisted analysis of carrierwaveuploader/carrierwave@b5f0abe10e (2026-08-21).
Data as JSON: /api/errors/df43768469a4261f.
Report an issue: GitHub.