class Cloudinary::Utils

Constants

ALGORITHM_SIGNATURE
ALGO_SHA1
ALGO_SHA256
AUDIO_FORMATS
CONDITIONAL_OPERATORS
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
EXP_REGEXP
EXP_REPLACEMENT
IMAGE_FORMATS
LAYER_KEYWORD_PARAMS
LONG_URL_SIGNATURE_LENGTH
MODE_DOWNLOAD
PREDEFINED_VARS
REMOTE_URL_REGEX
SHORT_URL_SIGNATURE_LENGTH
SIMPLE_TRANSFORMATION_PARAMS
TRANSFORMATION_PARAMS
UPLOAD_PREFIX
URL_KEYS
VIDEO_FORMATS

Public Class Methods

api_sign_request(params_to_sign, api_secret, signature_algorithm = nil) click to toggle source
# File lib/cloudinary/utils.rb, line 476
def self.api_sign_request(params_to_sign, api_secret, signature_algorithm = nil)
  to_sign = api_string_to_sign(params_to_sign)
  hash("#{to_sign}#{api_secret}", signature_algorithm, :hexdigest)
end
api_string_to_sign(params_to_sign) click to toggle source
# File lib/cloudinary/utils.rb, line 472
def self.api_string_to_sign(params_to_sign)
  params_to_sign.map{|k,v| [k.to_s, v.is_a?(Array) ? v.join(",") : v]}.reject{|k,v| v.nil? || v == ""}.sort_by(&:first).map{|k,v| "#{k}=#{v}"}.join("&")
end
archive_params(options = {}) click to toggle source

Returns a Hash of parameters used to create an archive @param [Hash] options @private

# File lib/cloudinary/utils.rb, line 1046
def self.archive_params(options = {})
  options = Cloudinary::Utils.symbolize_keys options
  {
    :timestamp=>(options[:timestamp] || Time.now.to_i),
    :type=>options[:type],
    :mode => options[:mode],
    :target_format => options[:target_format],
    :target_public_id=> options[:target_public_id],
    :flatten_folders=>Cloudinary::Utils.as_safe_bool(options[:flatten_folders]),
    :flatten_transformations=>Cloudinary::Utils.as_safe_bool(options[:flatten_transformations]),
    :use_original_filename=>Cloudinary::Utils.as_safe_bool(options[:use_original_filename]),
    :async=>Cloudinary::Utils.as_safe_bool(options[:async]),
    :notification_url=>options[:notification_url],
    :target_tags=>options[:target_tags] && Cloudinary::Utils.build_array(options[:target_tags]),
    :keep_derived=>Cloudinary::Utils.as_safe_bool(options[:keep_derived]),
    :tags=>options[:tags] && Cloudinary::Utils.build_array(options[:tags]),
    :public_ids=>options[:public_ids] && Cloudinary::Utils.build_array(options[:public_ids]),
    :fully_qualified_public_ids=>options[:fully_qualified_public_ids] && Cloudinary::Utils.build_array(options[:fully_qualified_public_ids]),
    :prefixes=>options[:prefixes] && Cloudinary::Utils.build_array(options[:prefixes]),
    :expires_at=>options[:expires_at],
    :transformations => build_eager(options[:transformations]),
    :skip_transformation_name=>Cloudinary::Utils.as_safe_bool(options[:skip_transformation_name]),
    :allow_missing=>Cloudinary::Utils.as_safe_bool(options[:allow_missing])
  }
end
as_bool(value) click to toggle source
# File lib/cloudinary/utils.rb, line 980
def self.as_bool(value)
  case value
  when nil then nil
  when String then value.downcase == "true" || value == "1"
  when TrueClass then true
  when FalseClass then false
  when Integer then value != 0
  when Symbol then value == :true
  else
    raise "Invalid boolean value #{value} of type #{value.class}"
  end
end
as_safe_bool(value) click to toggle source
# File lib/cloudinary/utils.rb, line 993
def self.as_safe_bool(value)
  case as_bool(value)
  when nil then nil
  when TrueClass then 1
  when FalseClass then 0
  end
end
asset_file_name(path) click to toggle source
# File lib/cloudinary/utils.rb, line 854
def self.asset_file_name(path)
  data = Cloudinary.app_root.join(path).read(:mode=>"rb")
  ext = path.extname
  md5 = Digest::MD5.hexdigest(data)
  public_id = "#{path.basename(ext)}-#{md5}"
  "#{public_id}#{ext}"
end
base_api_url(path, options = {}) click to toggle source

Creates a base URL for the cloudinary api

@param [Object] path Resource name @param [Hash] options Additional options

@return [String]

# File lib/cloudinary/utils.rb, line 731
def self.base_api_url(path, options = {})
  cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || UPLOAD_PREFIX
  cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, 'Must supply cloud_name')
  api_version = options[:api_version] || Cloudinary.config.api_version || 'v1_1'

  [cloudinary, api_version, cloud_name, path].join('/')
end
build_array(array) click to toggle source
# File lib/cloudinary/utils.rb, line 900
def self.build_array(array)
  case array
    when Array then array
    when nil then []
    else [array]
  end
end
build_distribution_domain(options = {}) click to toggle source
# File lib/cloudinary/utils.rb, line 711
def self.build_distribution_domain(options = {})
  cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")

  source = options.delete(:source)
  secure = config_option_consume(options, :secure, true)
  private_cdn = config_option_consume(options, :private_cdn)
  secure_distribution = config_option_consume(options, :secure_distribution)
  cname = config_option_consume(options, :cname)
  cdn_subdomain = config_option_consume(options, :cdn_subdomain)
  secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)

  unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
end
build_eager(eager) click to toggle source

@private @param [String|Hash|Array] eager an transformation as a string or hash, with or without a format. The parameter also accepts an array of eager transformations.

# File lib/cloudinary/utils.rb, line 1075
def self.build_eager(eager)
  return nil if eager.nil?
  Cloudinary::Utils.build_array(eager).map do
  |transformation, format|
    unless transformation.is_a? String
      transformation = transformation.clone
      if transformation.respond_to?(:delete)
        format = transformation.delete(:format) || format
      end
      transformation = Cloudinary::Utils.generate_transformation_string(transformation, true)
    end
    [transformation, format].compact.join("/")
  end.join("|")
end
build_multi_and_sprite_params(tag_or_options, options) click to toggle source

Build params for multi, download_multi, generate_sprite, and download_generated_sprite methods

@param [String|Hash] tag_or_options Treated as additional options when hash is passed, otherwise as a tag @param [Hash] options Additional options. Should be omitted when tag_or_options is a Hash

@return [Hash]

@private

# File lib/cloudinary/utils.rb, line 1251
def self.build_multi_and_sprite_params(tag_or_options, options)
  if tag_or_options.is_a?(Hash)
    if options.blank?
      options = tag_or_options
      tag_or_options = nil
    else
      raise "First argument must be a tag when additional options are passed"
    end
  end
  urls = options.delete(:urls)

  if tag_or_options.blank? && urls.blank?
    raise "Either tag or urls are required"
  end

  {
    :tag => tag_or_options,
    :urls => urls,
    :transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format])),
    :notification_url => options[:notification_url],
    :format => options[:format],
    :async => options[:async],
    :mode => options[:mode],
    :timestamp => (options[:timestamp] || Time.now.to_i)
  }
end
chain_transformation(options, *transformation) click to toggle source
# File lib/cloudinary/utils.rb, line 179
def self.chain_transformation(options, *transformation)
  base_options = extract_config_params(options)
  transformation = transformation.reject(&:nil?)
  base_options[:transformation] = build_array(extract_transformation_params(options)).concat(transformation)
  base_options
end
cloudinary_api_url(action = 'upload', options = {}) click to toggle source
# File lib/cloudinary/utils.rb, line 739
def self.cloudinary_api_url(action = 'upload', options = {})
  resource_type = options[:resource_type] || 'image'

  base_api_url([resource_type, action], options)
end
cloudinary_url(source, options = {}) click to toggle source

Warning: options are being destructively updated!

# File lib/cloudinary/utils.rb, line 514
def self.cloudinary_url(source, options = {})

  patch_fetch_format(options)
  type = options.delete(:type)

  transformation = self.generate_transformation_string(options)

  resource_type = options.delete(:resource_type)
  version = options.delete(:version)
  force_version = config_option_consume(options, :force_version, true)
  format = options.delete(:format)

  shorten = config_option_consume(options, :shorten)
  force_remote = options.delete(:force_remote)

  sign_url = config_option_consume(options, :sign_url)
  secret = config_option_consume(options, :api_secret)
  url_suffix = options.delete(:url_suffix)
  use_root_path = config_option_consume(options, :use_root_path)
  auth_token = config_option_consume(options, :auth_token)
  long_url_signature = config_option_consume(options, :long_url_signature)
  signature_algorithm = config_option_consume(options, :signature_algorithm)
  unless auth_token == false
    auth_token = Cloudinary::AuthToken.merge_auth_token(Cloudinary.config.auth_token, auth_token)
  end

  original_source = source
  return original_source if source.blank?
  if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
    resource_type ||= source.resource_type
    type ||= source.storage_type
    source = format.blank? ? source.filename : source.full_public_id
  end
  type = type.to_s unless type.nil?
  resource_type ||= "image"
  source = source.to_s
  unless force_remote
    static_support = Cloudinary.config.static_file_support || Cloudinary.config.static_image_support
    return original_source if !static_support && type == "asset"
    return original_source if (type.nil? || type == "asset") && source.match(%r(^https?:/)i)
    return original_source if source.match(%r(^/(?!images/).*)) # starts with / but not /images/

    source = source.sub(%r(^/images/), '') # remove /images/ prefix  - backwards compatibility
    if type == "asset"
      source, resource_type = Cloudinary::Static.public_id_and_resource_type_from_path(source)
      return original_source unless source # asset not found in Static
      source += File.extname(original_source) unless format
    end
  end

  resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
  source, source_to_sign = finalize_source(source, format, url_suffix)

  if version.nil? && force_version &&
       source_to_sign.include?("/") &&
       !source_to_sign.match(/^v[0-9]+/) &&
       !source_to_sign.match(/^https?:\//)
    version = 1
  end
  version &&= "v#{version}"

  transformation = transformation.gsub(%r(([^:])//), '\1/')
  if sign_url && ( !auth_token || auth_token.empty?)
    raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
    to_sign = [transformation, source_to_sign].reject(&:blank?).join("/")
    to_sign = fully_unescape(to_sign)
    signature_algorithm = long_url_signature ? ALGO_SHA256 : signature_algorithm
    hash = hash("#{to_sign}#{secret}", signature_algorithm)
    signature = Base64.urlsafe_encode64(hash)
    signature = "s--#{signature[0, long_url_signature ? LONG_URL_SIGNATURE_LENGTH : SHORT_URL_SIGNATURE_LENGTH ]}--"
  end

  options[:source] = source
  prefix = build_distribution_domain(options)

  source = [prefix, resource_type, type, signature, transformation, version, source].reject(&:blank?).join("/")

  token = nil
  if sign_url && auth_token && !auth_token.empty?
    auth_token[:url] = URI.parse(source).path
    token = Cloudinary::AuthToken.generate auth_token
  end

  analytics = config_option_consume(options, :analytics, true)
  analytics_token = nil
  if analytics && ! original_source.include?("?") # Disable analytics for public IDs containing query params.
    analytics_token = Cloudinary::Analytics.sdk_analytics_query_param
  end

  query_params = [token, analytics_token].compact.join("&")

  source += "?#{query_params}" unless query_params.empty?
  source
end
config_option_consume(options, option_name, default_value = nil) click to toggle source
# File lib/cloudinary/utils.rb, line 968
def self.config_option_consume(options, option_name, default_value = nil)
  return options.delete(option_name) if options.include?(option_name)
  option_value = Cloudinary.config.send(option_name)
  option_value.nil? ? default_value : option_value
end
config_option_fetch(options, option_name, default_value = nil) click to toggle source
# File lib/cloudinary/utils.rb, line 974
def self.config_option_fetch(options, option_name, default_value = nil)
  return options.fetch(option_name) if options.include?(option_name)
  option_value = Cloudinary.config.send(option_name)
  option_value.nil? ? default_value : option_value
end
deep_symbolize_keys(object) click to toggle source
# File lib/cloudinary/utils.rb, line 1027
def self.deep_symbolize_keys(object)
  case object
  when Hash
    result = {}
    object.each do |key, value|
      key = key.to_sym rescue key
      result[key] = deep_symbolize_keys(value)
    end
    result
  when Array
    object.map{|e| deep_symbolize_keys(e)}
  else
    object
  end
end
download_archive_url(options = {}) click to toggle source

Returns a URL that when invokes creates an archive and returns it. @param options [Hash] @option options [String|Symbol] :resource_type The resource type of files to include in the archive. Must be one of :image | :video | :raw @option options [String|Symbol] :type (:upload) The specific file type of resources: :upload|:private|:authenticated @option options [String|Symbol|Array] :tags (nil) list of tags to include in the archive @option options [String|Array<String>] :public_ids (nil) list of public_ids to include in the archive @option options [String|Array<String>] :prefixes (nil) Optional list of prefixes of public IDs (e.g., folders). @option options [String|Array<String>] :transformations Optional list of transformations.

The derived images of the given transformations are included in the archive. Using the string representation of
multiple chained transformations as we use for the 'eager' upload parameter.

@option options [String|Symbol] :mode (:create) return the generated archive file or to store it as a raw resource and

return a JSON with URLs for accessing the archive. Possible values: :download, :create

@option options [String|Symbol] :target_format (:zip) @option options [String] :target_public_id Optional public ID of the generated raw resource.

Relevant only for the create mode. If not specified, random public ID is generated.

@option options [boolean] :flatten_folders (false) If true, flatten public IDs with folders to be in the root of the archive.

Add numeric counter to the file name in case of a name conflict.

@option options [boolean] :flatten_transformations (false) If true, and multiple transformations are given,

flatten the folder structure of derived images and store the transformation details on the file name instead.

@option options [boolean] :use_original_filename Use the original file name of included images (if available) instead of the public ID. @option options [boolean] :async (false) If true, return immediately and perform the archive creation in the background.

Relevant only for the create mode.

@option options [String] :notification_url Optional URL to send an HTTP post request (webhook) when the archive creation is completed. @option options [String|Array<String] :target_tags Optional array. Allows assigning one or more tag to the generated archive file (for later housekeeping via the admin API). @option options [String] :keep_derived (false) keep the derived images used for generating the archive @return [String] archive url

# File lib/cloudinary/utils.rb, line 831
def self.download_archive_url(options = {})
  params = Cloudinary::Utils.archive_params(options)
  cloudinary_api_download_url("generate_archive", params, options)
end
download_backedup_asset(asset_id, version_id, options = {}) click to toggle source

The returned url should allow downloading the backedup asset based on the version and asset id

asset and version id are returned with resource(<PUBLIC_ID1>, { versions: true })

@param [String] asset_id Asset identifier @param [String] version_id Specific version of asset to download @param [Hash] options Additional options

@return [String] An url for downloading a file

# File lib/cloudinary/utils.rb, line 1312
def self.download_backedup_asset(asset_id, version_id, options = {})
  params = Cloudinary::Utils.sign_request({
    :timestamp => (options[:timestamp] || Time.now.to_i),
    :asset_id => asset_id,
    :version_id => version_id
  }, options)

  "#{Cloudinary::Utils.base_api_url("download_backup", options)}?#{Cloudinary::Utils.hash_query_params((params))}"
end
download_folder(folder_path, options = {}) click to toggle source

Creates and returns a URL that when invoked creates an archive of a folder.

@param [Object] folder_path Full path (from the root) of the folder to download. @param [Hash] options Additional options.

@return [String]

# File lib/cloudinary/utils.rb, line 848
def self.download_folder(folder_path, options = {})
  resource_type = options[:resource_type] || "all"

  download_archive_url(options.merge(:resource_type => resource_type, :prefixes => folder_path))
end
download_generated_sprite(tag, options = {}) click to toggle source

Return a signed URL to the ‘generate_sprite’ endpoint with ‘mode=download’.

@param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag @param [Hash] options Additional options. Should be omitted when tag_or_options is a Hash

@return [String] The signed URL to download sprite

# File lib/cloudinary/utils.rb, line 776
def self.download_generated_sprite(tag, options = {})
  params = build_multi_and_sprite_params(tag, options)
  cloudinary_api_download_url("sprite", params, options)
end
download_multi(tag, options = {}) click to toggle source

Return a signed URL to the ‘multi’ endpoint with ‘mode=download’.

@param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag @param [Hash] options Additional options. Should be omitted when tag_or_options is a Hash

@return [String] The signed URL to download multi

# File lib/cloudinary/utils.rb, line 787
def self.download_multi(tag, options = {})
  params = build_multi_and_sprite_params(tag, options)
  cloudinary_api_download_url("multi", params, options)
end
download_zip_url(options = {}) click to toggle source

Returns a URL that when invokes creates an zip archive and returns it. @see download_archive_url

# File lib/cloudinary/utils.rb, line 838
def self.download_zip_url(options = {})
  download_archive_url(options.merge(:target_format => "zip"))
end
encode_context(hash) click to toggle source

Same like encode_hash, with additional escaping of | and = characters @hash [Hash] key-value hash to be encoded @return [String] a joined string of all keys and values properly escaped and separated by a pipe character @private

# File lib/cloudinary/utils.rb, line 924
def self.encode_context(hash)
  case hash
    when Hash then hash.map{|k,v| "#{k}=#{v.to_s.gsub(/([=|])/, '\\\\\1')}"}.join("|")
    when nil then ""
    else hash
  end
end
encode_double_array(array) click to toggle source
# File lib/cloudinary/utils.rb, line 932
def self.encode_double_array(array)
  array = build_array(array)
  if array.length > 0 && array[0].is_a?(Array)
    return array.map{|a| build_array(a).join(",")}.join("|")
  else
    return array.join(",")
  end
end
encode_hash(hash) click to toggle source

encodes a hash into pipe-delimited key-value pairs string @hash [Hash] key-value hash to be encoded @return [String] a joined string of all keys and values separated by a pipe character @private

# File lib/cloudinary/utils.rb, line 912
def self.encode_hash(hash)
  case hash
    when Hash then hash.map{|k,v| "#{k}=#{v}"}.join("|")
    when nil then ""
    else hash
  end
end
extract_config_params(options) click to toggle source
# File lib/cloudinary/utils.rb, line 171
def self.extract_config_params(options)
    options.select{|k,v| URL_KEYS.include?(k)}
end
extract_transformation_params(options) click to toggle source
# File lib/cloudinary/utils.rb, line 175
def self.extract_transformation_params(options)
  options.select{|k,v| TRANSFORMATION_PARAMS.include?(k)}
end
finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten) click to toggle source
# File lib/cloudinary/utils.rb, line 629
def self.finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
  type ||= :upload
  if !url_suffix.blank?
    case
    when resource_type.to_s == "image" && type.to_s == "upload"
      resource_type = "images"
      type = nil
    when resource_type.to_s == "image" && type.to_s == "private"
      resource_type = "private_images"
      type = nil
    when resource_type.to_s == "image" && type.to_s == "authenticated"
      resource_type = "authenticated_images"
      type = nil
    when resource_type.to_s == "raw" && type.to_s == "upload"
      resource_type = "files"
      type = nil
    when resource_type.to_s == "video" && type.to_s == "upload"
      resource_type = "videos"
      type = nil
    else
      raise(CloudinaryException, "URL Suffix only supported for image/upload, image/private, image/authenticated, video/upload and raw/upload")
    end
  end
  if use_root_path
    if (resource_type.to_s == "image" && type.to_s == "upload") || (resource_type.to_s == "images" && type.blank?)
      resource_type = nil
      type = nil
    else
      raise(CloudinaryException, "Root path only supported for image/upload")
    end
  end
  if shorten && resource_type.to_s == "image" && type.to_s == "upload"
    resource_type = "iu"
    type = nil
  end
  [resource_type, type]
end
finalize_source(source, format, url_suffix) click to toggle source
# File lib/cloudinary/utils.rb, line 609
def self.finalize_source(source, format, url_suffix)
  source = source.gsub(%r(([^:])//), '\1/')
  if source.match(%r(^https?:/)i)
    source = smart_escape(source)
    source_to_sign = source
  else
    source = smart_escape(smart_unescape(source))
    source_to_sign = source
    unless url_suffix.blank?
      raise(CloudinaryException, "url_suffix should not include . or /") if url_suffix.match(%r([\./]))
      source = "#{source}/#{url_suffix}"
    end
    if !format.blank?
      source = "#{source}.#{format}"
      source_to_sign = "#{source_to_sign}.#{format}"
    end
  end
  [source, source_to_sign]
end
flat_hash_to_query_params(hash) click to toggle source
# File lib/cloudinary/utils.rb, line 1121
def self.flat_hash_to_query_params(hash)
  hash.collect do |key, value|
    if value.is_a?(Array)
      value.map{|v| "#{CGI.escape(key.to_s)}[]=#{CGI.escape(v.to_s)}"}.join("&")
    else
      "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
    end
  end.compact.sort!.join('&')
end
generate_auth_token(options) click to toggle source
# File lib/cloudinary/utils.rb, line 1090
def self.generate_auth_token(options)
  options = Cloudinary::AuthToken.merge_auth_token Cloudinary.config.auth_token, options
  Cloudinary::AuthToken.generate options

end
generate_responsive_breakpoints_string(breakpoints) click to toggle source
# File lib/cloudinary/utils.rb, line 495
def self.generate_responsive_breakpoints_string(breakpoints)
  return nil if breakpoints.nil?
  breakpoints = build_array(breakpoints)

  breakpoints.map do |breakpoint_settings|
    unless breakpoint_settings.nil?
      breakpoint_settings = breakpoint_settings.clone
      transformation =  breakpoint_settings.delete(:transformation) || breakpoint_settings.delete("transformation")
      format =  breakpoint_settings.delete(:format) || breakpoint_settings.delete("format")
      if transformation
        transformation = Cloudinary::Utils.generate_transformation_string(transformation.clone, true)
      end
      breakpoint_settings[:transformation] = [transformation, format].compact.join("/")
    end
    breakpoint_settings
  end.to_json
end
generate_transformation_string(options={}, allow_implicit_crop_mode = false) click to toggle source

Warning: options are being destructively updated!

# File lib/cloudinary/utils.rb, line 188
def self.generate_transformation_string(options={}, allow_implicit_crop_mode = false)
  # allow_implicit_crop_mode was added to support height and width parameters without specifying a crop mode.
  # This only apply to this (cloudinary_gem) SDK

  if options.is_a?(Array)
    return options.map{|base_transformation| generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode)}.reject(&:blank?).join("/")
  end

  symbolize_keys!(options)

  responsive_width = config_option_consume(options, :responsive_width)
  size = options.delete(:size)
  options[:width], options[:height] = size.split("x") if size
  width = options[:width]
  width = width.to_s if width.is_a?(Symbol)
  height = options[:height]
  has_layer = options[:overlay].present? || options[:underlay].present?

  crop = options.delete(:crop)
  angle = build_array(options.delete(:angle)).join(".")

  no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
  options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width.to_s.start_with?("auto") || responsive_width)
  options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)

  width=height=nil if crop.nil? && !has_layer && !width.to_s.start_with?("auto") && !allow_implicit_crop_mode

  background = options.delete(:background)
  background = background.sub(/^#/, 'rgb:') if background

  color = options.delete(:color)
  color = color.sub(/^#/, 'rgb:') if color

  base_transformations = build_array(options.delete(:transformation))
  if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
    base_transformations = base_transformations.map do
      |base_transformation|
      base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode) : generate_transformation_string({:transformation=>base_transformation}, allow_implicit_crop_mode)
    end
  else
    named_transformation = base_transformations.join(".")
    base_transformations = []
  end

  effect = options.delete(:effect)
  effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)

  border = options.delete(:border)
  if border.is_a?(Hash)
    border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
  elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
    options[:border] = border
    border = nil
  end
  flags = build_array(options.delete(:flags)).join(".")
  dpr = config_option_consume(options, :dpr)

  if options.include? :offset
    options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
  end

  fps = options.delete(:fps)
  fps = fps.join('-') if fps.is_a? Array

  overlay = process_layer(options.delete(:overlay))
  underlay = process_layer(options.delete(:underlay))
  ifValue = process_if(options.delete(:if))
  custom_function = process_custom_function(options.delete(:custom_function))
  custom_pre_function = process_custom_pre_function(options.delete(:custom_pre_function))

  params = {
    :a   => normalize_expression(angle),
    :ar => normalize_expression(options.delete(:aspect_ratio)),
    :b   => background,
    :bo  => border,
    :c   => crop,
    :co  => color,
    :dpr => normalize_expression(dpr),
    :e   => normalize_expression(effect),
    :fl  => flags,
    :fn  => custom_function || custom_pre_function,
    :fps => fps,
    :h   => normalize_expression(height),
    :l  => overlay,
    :o => normalize_expression(options.delete(:opacity)),
    :q => normalize_expression(options.delete(:quality)),
    :r => process_radius(options.delete(:radius)),
    :t   => named_transformation,
    :u  => underlay,
    :w   => normalize_expression(width),
    :x => normalize_expression(options.delete(:x)),
    :y => normalize_expression(options.delete(:y)),
    :z => normalize_expression(options.delete(:zoom))
  }
  SIMPLE_TRANSFORMATION_PARAMS.each do
    |param, option|
    params[param] = options.delete(option)
  end

  params[:vc] = process_video_params params[:vc] if params[:vc].present?
  [:so, :eo, :du].each do |range_value|
    params[range_value] = norm_range_value params[range_value] if params[range_value].present?
  end

  variables = options.delete(:variables)
  var_params = []
  options.each_pair do |key, value|
    if key =~ /^\$/
      var_params.push "#{key}_#{normalize_expression(value.to_s)}"
    end
  end
  var_params.sort!
  unless variables.nil? || variables.empty?
    for name, value in variables
      var_params.push "#{name}_#{normalize_expression(value.to_s)}"
    end
  end
  variables = var_params.join(',')

  raw_transformation = options.delete(:raw_transformation)
  transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort
  transformation = transformation.join(",")
  transformation = [ifValue, variables, transformation, raw_transformation].reject(&:blank?).join(",")

  transformations = base_transformations << transformation
  if responsive_width
    responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
    transformations << generate_transformation_string(responsive_width_transformation.clone, allow_implicit_crop_mode)
  end

  if width.to_s.start_with?( "auto") || responsive_width
    options[:responsive] = true
  end
  if dpr.to_s == "auto"
    options[:hidpi] = true
  end

  transformations.reject(&:blank?).join("/")
end
handle_file_param(file, options = {}) click to toggle source

Handles file parameter.

@param [Pathname, StringIO, File, String, int, _ToPath] file @return [StringIO, File, String] A File object.

@private

# File lib/cloudinary/utils.rb, line 1284
def self.handle_file_param(file, options = {})
  original_filename = options[:original_filename] || "cloudinaryfile"
  content_type = options[:content_type] || "application/octet-stream"
  if file.is_a?(Pathname)
    return Faraday::FilePart.new(file.to_s, content_type)
  elsif file.is_a?(Cloudinary::Blob)
    return Faraday::FilePart.new(file, file.content_type, file.original_filename)
  elsif file.is_a?(StringIO)
    file.rewind
    return Faraday::FilePart.new(file, content_type, original_filename)
  elsif file.respond_to?(:read)
    return Faraday::FilePart.new(file, content_type, original_filename)
  elsif Cloudinary::Utils.is_remote?(file)
    return file.to_s
  end
  # we got file path
  Faraday::FilePart.new(file.to_s, content_type)
end
hash(input, signature_algorithm = nil, hash_method = :digest) click to toggle source

Computes hash from input string using specified algorithm.

@param [String] input String which to compute hash from @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash @param [Symbol] hash_method Hash method applied to a signature algorithm (:digest or :hexdigest)

@return [String] Computed hash value

# File lib/cloudinary/utils.rb, line 1386
def self.hash(input, signature_algorithm = nil, hash_method = :digest)
  signature_algorithm ||= Cloudinary.config.signature_algorithm || ALGO_SHA1
  algorithm = ALGORITHM_SIGNATURE[signature_algorithm] || raise("Unsupported algorithm '#{signature_algorithm}'")
  algorithm.public_send(hash_method, input)
end
hash_query_params(hash) click to toggle source
# File lib/cloudinary/utils.rb, line 1113
def self.hash_query_params(hash)
  if hash.respond_to?("to_query")
    hash.to_query
  else
    flat_hash_to_query_params(hash)
  end
end
is_remote?(url) click to toggle source
# File lib/cloudinary/utils.rb, line 1239
def self.is_remote?(url)
  REMOTE_URL_REGEX === url
end
json_array_param(data) { |data| ... } click to toggle source

Returns a JSON array as String. Yields the array before it is converted to JSON format @api private @param [Hash|String|Array<Hash>] data @return [String|nil] a JSON array string or ‘nil` if data is `nil`

# File lib/cloudinary/utils.rb, line 486
def self.json_array_param(data)
  return nil if data.nil?

  data = JSON.parse(data) if data.is_a?(String)
  data = [data] unless data.is_a?(Array)
  data = yield data if block_given?
  JSON.generate(data)
end
json_decode(str) click to toggle source
# File lib/cloudinary/utils.rb, line 884
def self.json_decode(str)
  if !@@json_decode
    @@json_decode = true
    begin
      require 'json'
    rescue LoadError
      begin
        require 'active_support/json'
      rescue LoadError
        raise LoadError, "Please add the json gem or active_support to your Gemfile"
      end
    end
  end
  defined?(JSON) ? JSON.parse(str) : ActiveSupport::JSON.decode(str)
end
normalize_expression(expression) click to toggle source
# File lib/cloudinary/utils.rb, line 339
def self.normalize_expression(expression)
  if expression.nil?
    nil
  elsif expression.is_a?( String) && expression =~ /^!.+!$/ # quoted string
    expression
  else
    expression.to_s.gsub(EXP_REGEXP) { |match| EXP_REPLACEMENT[match] || match }.gsub(/[ _]+/, "_")
  end
end
patch_fetch_format(options={}) click to toggle source

Handle the format parameter for fetch urls @private @param options url and transformation options. This argument may be changed by the function!

# File lib/cloudinary/utils.rb, line 1231
def self.patch_fetch_format(options={})
  use_fetch_format = config_option_consume(options, :use_fetch_format)
  if options[:type] === :fetch || use_fetch_format
    format_arg = options.delete(:format)
    options[:fetch_format] ||= format_arg
  end
end
private_download_url(public_id, format, options = {}) click to toggle source
# File lib/cloudinary/utils.rb, line 792
def self.private_download_url(public_id, format, options = {})
  cloudinary_params = sign_request({
      :timestamp=>Time.now.to_i,
      :public_id=>public_id,
      :format=>format,
      :type=>options[:type],
      :attachment=>options[:attachment],
      :expires_at=>options[:expires_at] && options[:expires_at].to_i
    }, options)

  return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + hash_query_params(cloudinary_params)
end
process_custom_function(param) click to toggle source
# File lib/cloudinary/utils.rb, line 1216
def self.process_custom_function(param)
  return param unless param.is_a? Hash

  function_type = param[:function_type]
  source = param[:source]

  source = Base64.urlsafe_encode64(source) if function_type == "remote"
  "#{function_type}:#{source}"
end
process_custom_pre_function(param) click to toggle source
# File lib/cloudinary/utils.rb, line 1211
def self.process_custom_pre_function(param)
  value = process_custom_function(param)
  value ? "pre:#{value}" : nil
end
process_if(if_value) click to toggle source

Parse “if” parameter Translates the condition if provided. @return [string] “if_” + ifValue @private

# File lib/cloudinary/utils.rb, line 332
def self.process_if(if_value)
  "if_" + normalize_expression(if_value) unless if_value.to_s.empty?
end
random_public_id() click to toggle source
# File lib/cloudinary/utils.rb, line 874
def self.random_public_id
  sr = defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom
  sr.base64(20).downcase.gsub(/[^a-z0-9]/, "").sub(/^[0-9]+/, '')[0,20]
end
resource_type_for_format(format) click to toggle source
# File lib/cloudinary/utils.rb, line 957
def self.resource_type_for_format(format)
  case
  when self.supported_format?(format, IMAGE_FORMATS)
    'image'
  when self.supported_format?(format, VIDEO_FORMATS), self.supported_format?(format, AUDIO_FORMATS)
    'video'
  else
    'raw'
  end
end
safe_blank?(value) click to toggle source
# File lib/cloudinary/utils.rb, line 1001
def self.safe_blank?(value)
  value.nil? || value == "" || value == []
end
sign_request(params, options={}) click to toggle source
# File lib/cloudinary/utils.rb, line 745
def self.sign_request(params, options={})
  api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
  signature_algorithm = options[:signature_algorithm]
  params = params.reject{|k, v| self.safe_blank?(v)}
  params[:signature] = api_sign_request(params, api_secret, signature_algorithm)
  params[:api_key] = api_key
  params
end
signed_preloaded_image(result) click to toggle source
# File lib/cloudinary/utils.rb, line 879
def self.signed_preloaded_image(result)
  "#{result["resource_type"]}/#{result["type"] || "upload"}/v#{result["version"]}/#{[result["public_id"], result["format"]].reject(&:blank?).join(".")}##{result["signature"]}"
end
smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/) click to toggle source

Based on CGI::escape. In addition does not escape / :

# File lib/cloudinary/utils.rb, line 863
def self.smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/)
  string.gsub(unsafe) do |m|
    '%' + m.unpack('H2' * m.bytesize).join('%').upcase
  end
end
smart_unescape(string) click to toggle source

Based on CGI::unescape. In addition keeps ‘+’ character as is

# File lib/cloudinary/utils.rb, line 870
def self.smart_unescape(string)
  CGI.unescape(string.gsub('+', '%2B'))
end
supported_format?( format, formats) click to toggle source
# File lib/cloudinary/utils.rb, line 951
def self.supported_format?( format, formats)
  format = format.to_s.downcase
  extension = format =~ /\./ ? format.split('.').last : format
  formats.include?(extension)
end
supported_image_format?(format) click to toggle source
# File lib/cloudinary/utils.rb, line 947
def self.supported_image_format?(format)
  supported_format? format, IMAGE_FORMATS
end
symbolize_keys(h) click to toggle source
# File lib/cloudinary/utils.rb, line 1005
def self.symbolize_keys(h)
  new_h = Hash.new
  if (h.respond_to? :keys)
    h.keys.each do |key|
      new_h[(key.to_sym rescue key)] = h[key]
    end
  end
  new_h
end
symbolize_keys!(h) click to toggle source
# File lib/cloudinary/utils.rb, line 1016
def self.symbolize_keys!(h)
  if (h.respond_to? :keys) && (h.respond_to? :delete)
    h.keys.each do |key|
      value = h.delete(key)
      h[(key.to_sym rescue key)] = value
    end
  end
  h
end
text_style(layer) click to toggle source
# File lib/cloudinary/utils.rb, line 445
def self.text_style(layer)
  return layer[:text_style] if layer[:text_style].present?

  font_family = layer[:font_family]
  font_size   = layer[:font_size]
  keywords    = []
  LAYER_KEYWORD_PARAMS.each do |attr, default_value|
    attr_value = layer[attr] || default_value
    keywords.push(attr_value) unless attr_value == default_value
  end
  letter_spacing = layer[:letter_spacing]
  keywords.push("letter_spacing_#{letter_spacing}") unless letter_spacing.blank?
  line_spacing = layer[:line_spacing]
  keywords.push("line_spacing_#{line_spacing}") unless line_spacing.blank?
  font_antialiasing = layer[:font_antialiasing]
  keywords.push("antialias_#{font_antialiasing}") unless font_antialiasing.blank?
  font_hinting = layer[:font_hinting]
  keywords.push("hinting_#{font_hinting}") unless font_hinting.blank?
  if !font_size.blank? || !font_family.blank? || !keywords.empty?
    raise(CloudinaryException, "Must supply font_family for text in overlay/underlay") if font_family.blank?
    raise(CloudinaryException, "Must supply font_size for text in overlay/underlay") if font_size.blank?
    keywords.unshift(font_size)
    keywords.unshift(font_family)
    keywords.reject(&:blank?).join("_")
  end
end
to_usage_api_date_format(date) click to toggle source

Format date in a format accepted by the usage API (e.g., 31-12-2020) if passed value is of type Date, otherwise return the string representation of the input.

@param [Date|Object] date @return [String]

# File lib/cloudinary/utils.rb, line 1328
def self.to_usage_api_date_format(date)
  if date.is_a?(Date)
    date.strftime('%d-%m-%Y')
  else
    date.to_s
  end
end
unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution) click to toggle source

Creates the URL prefix for the cloudinary resource URL

cdn_subdomain and secure_cdn_subdomain

  1. Customers in shared distribution (e.g. res.cloudinary.com)

    if cdn_domain is true uses res-[1-5 ].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https.

  2. Customers with private cdn

    if cdn_domain is true uses cloudname-res-[1-5 ].cloudinary.com for http

    if secure_cdn_domain is true uses cloudname-res-[1-5 ].cloudinary.com for https (please contact support if you require this)

  3. Customers with cname

    if cdn_domain is true uses a.cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.

@private

# File lib/cloudinary/utils.rb, line 682
def self.unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
  return "/res#{cloud_name}" if cloud_name.start_with?("/") # For development

  shared_domain = !private_cdn

  if secure
    if secure_distribution.nil?
      secure_distribution = private_cdn ? "#{cloud_name}-res.cloudinary.com" : Cloudinary::SHARED_CDN
    end
    shared_domain ||= secure_distribution == Cloudinary::SHARED_CDN
    secure_cdn_subdomain = cdn_subdomain if secure_cdn_subdomain.nil? && shared_domain

    if secure_cdn_subdomain
      secure_distribution = secure_distribution.gsub('res.cloudinary.com', "res-#{(Zlib::crc32(source) % 5) + 1}.cloudinary.com")
    end

    prefix = "https://#{secure_distribution}"
  elsif cname
    subdomain = cdn_subdomain ? "a#{(Zlib::crc32(source) % 5) + 1}." : ""
    prefix = "http://#{subdomain}#{cname}"
  else
    host = [private_cdn ? "#{cloud_name}-" : "", "res", cdn_subdomain ? "-#{(Zlib::crc32(source) % 5) + 1}" : "", ".cloudinary.com"].join
    prefix = "http://#{host}"
  end
  prefix += "/#{cloud_name}" if shared_domain

  prefix
end
verify_api_response_signature(public_id, version, signature, signature_algorithm = nil, options = {}) click to toggle source

Verifies the authenticity of an API response signature.

@param [String] public_id he public id of the asset as returned in the API response @param [Fixnum] version The version of the asset as returned in the API response @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash @param [Hash] options @option options [String] :api_secret API secret, if not passed taken from global config

@return [Boolean]

# File lib/cloudinary/utils.rb, line 1346
def self.verify_api_response_signature(public_id, version, signature, signature_algorithm = nil, options = {})
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")

  parameters_to_sign = {
    :public_id => public_id,
    :version => version
  }

  signature == api_sign_request(parameters_to_sign, api_secret, signature_algorithm)
end
verify_notification_signature(body, timestamp, signature, valid_for = 7200, signature_algorithm = nil, options = {}) click to toggle source

Verifies the authenticity of a notification signature.

@param [String] body JSON of the request’s body @param [Fixnum] timestamp Unix timestamp. Can be retrieved from the X-Cld-Timestamp header @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header @param [Fixnum] valid_for The desired time in seconds for considering the request valid @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash @param [Hash] options @option options [String] :api_secret API secret, if not passed taken from global config

@return [Boolean]

# File lib/cloudinary/utils.rb, line 1368
def self.verify_notification_signature(body, timestamp, signature, valid_for = 7200, signature_algorithm = nil, options = {})
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
  raise("Body should be of String type") unless body.is_a?(String)
  # verify that signature is valid for the given timestamp
  return false if timestamp < (Time.now - valid_for).to_i

  payload_hash = hash("#{body}#{timestamp}#{api_secret}", signature_algorithm, :hexdigest)

  signature == payload_hash
end

Private Class Methods

cloudinary_api_download_url(action, params, options = {}) click to toggle source

Helper method for generating download URLs

@param [String] action @see Cloudinary::Utils.cloudinary_api_url @param [Hash] params Query parameters in generated URL @param [Hash] options Additional options @yield [query_parameters] Invokes the block with query parameters to override how to encode them

@return [String]

# File lib/cloudinary/utils.rb, line 763
def self.cloudinary_api_download_url(action, params, options = {})
  cloudinary_params = sign_request(params.merge(mode: MODE_DOWNLOAD), options)

  "#{Cloudinary::Utils.cloudinary_api_url(action, options)}?#{hash_query_params(cloudinary_params)}"
end
fully_unescape(source) click to toggle source

Repeatedly unescapes the source until no more unescaping is possible or 10 cycles elapsed @param [String] source - a (possibly) escaped string @return [String] the fully unescaped string @private

# File lib/cloudinary/utils.rb, line 1103
def self.fully_unescape(source)
  i = 0
  while source != CGI.unescape(source.gsub('+', '%2B')) && i <10
    source = CGI.unescape(source.gsub('+', '%2B')) # don't let unescape replace '+' with space
    i = i + 1
  end
  source
end
number_pattern() click to toggle source
# File lib/cloudinary/utils.rb, line 1131
def self.number_pattern
  "([0-9]*)\\.([0-9]+)|([0-9]+)"
end
offset_any_pattern() click to toggle source
# File lib/cloudinary/utils.rb, line 1136
def self.offset_any_pattern
  "(#{number_pattern})([%pP])?"
end
offset_any_pattern_re() click to toggle source
# File lib/cloudinary/utils.rb, line 1141
def self.offset_any_pattern_re
  /((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)\.\.((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)/
end
process_layer(layer) click to toggle source

Parse layer options @return [string] layer transformation string @private

# File lib/cloudinary/utils.rb, line 352
def self.process_layer(layer)
   if layer.is_a? String and layer.start_with?("fetch:")
    layer = {:url => layer[6..-1]} # omit "fetch:" prefix
   end
   if layer.is_a? Hash
     layer = symbolize_keys layer
     public_id     = layer[:public_id]
     format        = layer[:format]
     fetch         = layer[:url]
     resource_type = layer[:resource_type] || "image"
     type          = layer[:type]
     text          = layer[:text]
     text_style    = nil
     components    = []

     if type.nil?
       if fetch.nil?
         type = "upload"
       else
         type = "fetch"
       end
     end

     if public_id.present?
        if type == "fetch" and public_id.match(%r(^https?:/)i)
          public_id = Base64.urlsafe_encode64(public_id)
        else
          public_id = public_id.gsub("/", ":")
          public_id = "#{public_id}.#{format}" if format
        end
     end

     if fetch.present? && fetch.match(%r(^https?:/)i)
       fetch = Base64.urlsafe_encode64(fetch)
     elsif text.blank? && resource_type != "text"
       if public_id.blank? && type != "fetch"
         raise(CloudinaryException, "Must supply public_id for resource_type layer_parameter")
       end
       if resource_type == "subtitles"
         text_style = text_style(layer)
       end
     else
       resource_type = "text"
       type          = nil
       # // type is ignored for text layers
       text_style    = text_style(layer)
       unless text.blank?
         unless public_id.blank? ^ text_style.blank?
           raise(CloudinaryException, "Must supply either style parameters or a public_id when providing text parameter in a text overlay/underlay")
         end

         result = ""
         # Don't encode interpolation expressions e.g. $(variable)
         while(/\$\([a-zA-Z]\w+\)/.match text) do
           match = Regexp.last_match
           result += smart_escape smart_escape(match.pre_match, %r"([,/])") # append encoded pre-match
           result += match.to_s # append match
           text = match.post_match
         end
         text = result + smart_escape( smart_escape(text, %r"([,/])"))
       end
     end
     components.push(resource_type) if resource_type != "image"
     components.push(type) if type != "upload"
     components.push(text_style)
     components.push(public_id)
     components.push(fetch)
     components.push(text)
     layer = components.reject(&:blank?).join(":")
   end
   layer
end
process_radius(radius) click to toggle source

Parse radius options @return [string] radius transformation string @private

# File lib/cloudinary/utils.rb, line 429
def self.process_radius(radius)
  if radius.is_a?(Array) && !radius.length.between?(1, 4)
    raise(CloudinaryException, "Invalid radius parameter")
  end
  Array(radius).map { |r| normalize_expression(r) }.join(":")
end
process_video_params(param) click to toggle source

A video codec parameter can be either a String or a Hash.

@param [Object] param vc_<codec>[ : <profile> : [<level> : [<b_frames>]]]

or <code>{ codec: 'h264', profile: 'basic', level: '3.1' }</code>
or <code>{ codec: 'h265', profile: 'auto', level: 'auto', b_frames: false }</code>

@return [String] <codec> : <profile> : [<level> : [<b_frames>]]] if a Hash was provided

or the param if a String was provided.
Returns NIL if param is not a Hash or String

@private

# File lib/cloudinary/utils.rb, line 1186
def self.process_video_params(param)
  case param
  when Hash
    video = ""
    if param.has_key? :codec
      video = param[:codec]
      if param.has_key? :profile
        video.concat ":" + param[:profile]
        if param.has_key? :level
          video.concat ":" + param[:level]
          if param.has_key?(:b_frames) && param[:b_frames] === false
            video.concat ":bframes_no"
          end
        end
      end
    end
    video
  when String
    param
  else
    nil
  end
end