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

  1. Normalize/encode the URL before passing it: Addressable::URI.normalize(source).to_s
  2. Validate the param parses as a URI (and scheme is http/https) before assigning it to the uploader
  3. Strip whitespace from user input before download
  4. 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

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


AI-assisted analysis of carrierwaveuploader/carrierwave@b5f0abe10e (2026-08-21). Data as JSON: /api/errors/df43768469a4261f. Report an issue: GitHub.