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
Source
# File lib/cloudinary/utils.rb, line 513 def self.api_sign_request(params_to_sign, api_secret, signature_algorithm = nil, signature_version = nil) signature_version ||= Cloudinary.config.signature_version || 2 to_sign = api_string_to_sign(params_to_sign, signature_version) hash("#{to_sign}#{api_secret}", signature_algorithm, :hexdigest) end
Signs API request parameters
@param [Hash] params_to_sign Parameters to include in the signature @param [String] api_secret API secret for signing @param [Symbol|nil] signature_algorithm Hash
algorithm to use (:sha1 or :sha256) @param [Integer|nil] signature_version Version of signature algorithm to use:
- Version 1: Original behavior without parameter encoding - Version 2+ (default): Includes parameter encoding to prevent parameter smuggling
@return [String] Hexadecimal signature @private
Source
# File lib/cloudinary/utils.rb, line 492 def self.api_string_to_sign(params_to_sign, signature_version = 2) 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| param_string = "#{k}=#{v}" signature_version >= 2 ? encode_param(param_string) : param_string } .join("&") end
Generates a string to be signed for API requests
@param [Hash] params_to_sign Parameters to include in the signature @param [Integer] signature_version Version of signature algorithm to use:
- Version 1: Original behavior without parameter encoding - Version 2+ (default): Includes parameter encoding to prevent parameter smuggling
@return [String] String
to be signed @private
Source
# File lib/cloudinary/utils.rb, line 1085 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
Returns a Hash
of parameters used to create an archive @param [Hash] options @private
Source
# File lib/cloudinary/utils.rb, line 1019 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
Source
# File lib/cloudinary/utils.rb, line 1032 def self.as_safe_bool(value) case as_bool(value) when nil then nil when TrueClass then 1 when FalseClass then 0 end end
Source
# File lib/cloudinary/utils.rb, line 893 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
Source
# File lib/cloudinary/utils.rb, line 769 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
Creates a base URL for the cloudinary api
@param [Object] path Resource name @param [Hash] options Additional options
@return [String]
Source
# File lib/cloudinary/utils.rb, line 939 def self.build_array(array) case array when Array then array when nil then [] else [array] end end
Source
# File lib/cloudinary/utils.rb, line 749 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
Source
# File lib/cloudinary/utils.rb, line 1114 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
@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.
Source
# File lib/cloudinary/utils.rb, line 1290 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
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
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
Source
# File lib/cloudinary/utils.rb, line 777 def self.cloudinary_api_url(action = 'upload', options = {}) resource_type = options[:resource_type] || 'image' base_api_url([resource_type, action], options) end
Source
# File lib/cloudinary/utils.rb, line 552 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
Warning: options are being destructively updated!
Source
# File lib/cloudinary/utils.rb, line 1007 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
Source
# File lib/cloudinary/utils.rb, line 1013 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
Source
# File lib/cloudinary/utils.rb, line 1066 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
Source
# File lib/cloudinary/utils.rb, line 870 def self.download_archive_url(options = {}) params = Cloudinary::Utils.archive_params(options) cloudinary_api_download_url("generate_archive", params, options) end
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
Source
# File lib/cloudinary/utils.rb, line 1351 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
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
Source
# File lib/cloudinary/utils.rb, line 887 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
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]
Source
# File lib/cloudinary/utils.rb, line 815 def self.download_generated_sprite(tag, options = {}) params = build_multi_and_sprite_params(tag, options) cloudinary_api_download_url("sprite", params, options) end
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
Source
# File lib/cloudinary/utils.rb, line 826 def self.download_multi(tag, options = {}) params = build_multi_and_sprite_params(tag, options) cloudinary_api_download_url("multi", params, options) end
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
Source
# File lib/cloudinary/utils.rb, line 877 def self.download_zip_url(options = {}) download_archive_url(options.merge(:target_format => "zip")) end
Returns a URL that when invokes creates an zip archive and returns it. @see download_archive_url
Source
# File lib/cloudinary/utils.rb, line 963 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
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
Source
# File lib/cloudinary/utils.rb, line 971 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
Source
# File lib/cloudinary/utils.rb, line 951 def self.encode_hash(hash) case hash when Hash then hash.map{|k,v| "#{k}=#{v}"}.join("|") when nil then "" else hash end end
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
Source
# File lib/cloudinary/utils.rb, line 480 def self.encode_param(value) value.to_s.gsub("&", "%26") end
Encodes a parameter for safe inclusion in URL query strings.
Specifically replaces “&” characters with their percent-encoded equivalent “%26” to prevent them from being interpreted as parameter separators in URL query strings.
@param [Object] value The parameter to encode @return [String] Encoded parameter @private
Source
# File lib/cloudinary/utils.rb, line 171 def self.extract_config_params(options) options.select{|k,v| URL_KEYS.include?(k)} end
Source
# File lib/cloudinary/utils.rb, line 175 def self.extract_transformation_params(options) options.select{|k,v| TRANSFORMATION_PARAMS.include?(k)} end
Source
# File lib/cloudinary/utils.rb, line 667 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
Source
# File lib/cloudinary/utils.rb, line 647 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
Source
# File lib/cloudinary/utils.rb, line 1160 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
Source
# File lib/cloudinary/utils.rb, line 1129 def self.generate_auth_token(options) options = Cloudinary::AuthToken.merge_auth_token Cloudinary.config.auth_token, options Cloudinary::AuthToken.generate options end
Source
# File lib/cloudinary/utils.rb, line 533 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
Source
# 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
Warning: options are being destructively updated!
Source
# File lib/cloudinary/utils.rb, line 1323 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
Handles file parameter.
@param [Pathname, StringIO, File, String
, int, _ToPath] file @return [StringIO, File, String] A File object.
@private
Source
# File lib/cloudinary/utils.rb, line 1425 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
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
Source
# File lib/cloudinary/utils.rb, line 1152 def self.hash_query_params(hash) if hash.respond_to?("to_query") hash.to_query else flat_hash_to_query_params(hash) end end
Source
# File lib/cloudinary/utils.rb, line 1278 def self.is_remote?(url) REMOTE_URL_REGEX === url end
Source
# File lib/cloudinary/utils.rb, line 524 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
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`
Source
# File lib/cloudinary/utils.rb, line 923 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
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
Source
# File lib/cloudinary/utils.rb, line 1270 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
Handle the format parameter for fetch urls @private @param options url and transformation options. This argument may be changed by the function!
Source
# File lib/cloudinary/utils.rb, line 831 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
Source
# File lib/cloudinary/utils.rb, line 1255 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
Source
# File lib/cloudinary/utils.rb, line 1250 def self.process_custom_pre_function(param) value = process_custom_function(param) value ? "pre:#{value}" : nil end
Source
# 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
Parse “if” parameter Translates the condition if provided. @return [string] “if_” + ifValue @private
Source
# File lib/cloudinary/utils.rb, line 913 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
Source
# File lib/cloudinary/utils.rb, line 996 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
Source
# File lib/cloudinary/utils.rb, line 1040 def self.safe_blank?(value) value.nil? || value == "" || value == [] end
Source
# File lib/cloudinary/utils.rb, line 783 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] signature_version = options[:signature_version] params = params.reject{|k, v| self.safe_blank?(v)} params[:signature] = api_sign_request(params, api_secret, signature_algorithm, signature_version) params[:api_key] = api_key params end
Source
# File lib/cloudinary/utils.rb, line 918 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
Source
# File lib/cloudinary/utils.rb, line 902 def self.smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/) string.gsub(unsafe) do |m| '%' + m.unpack('H2' * m.bytesize).join('%').upcase end end
Based on CGI::escape. In addition does not escape / :
Source
# File lib/cloudinary/utils.rb, line 909 def self.smart_unescape(string) CGI.unescape(string.gsub('+', '%2B')) end
Based on CGI::unescape. In addition keeps ‘+’ character as is
Source
# File lib/cloudinary/utils.rb, line 990 def self.supported_format?( format, formats) format = format.to_s.downcase extension = format =~ /\./ ? format.split('.').last : format formats.include?(extension) end
Source
# File lib/cloudinary/utils.rb, line 986 def self.supported_image_format?(format) supported_format? format, IMAGE_FORMATS end
Source
# File lib/cloudinary/utils.rb, line 1044 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
Source
# File lib/cloudinary/utils.rb, line 1055 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
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
Source
# File lib/cloudinary/utils.rb, line 1367 def self.to_usage_api_date_format(date) if date.is_a?(Date) date.strftime('%d-%m-%Y') else date.to_s end end
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]
Source
# File lib/cloudinary/utils.rb, line 720 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
Creates the URL prefix for the cloudinary resource URL
cdn_subdomain and secure_cdn_subdomain
-
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.
-
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)
-
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
Source
# File lib/cloudinary/utils.rb, line 1385 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, 1) end
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]
Source
# File lib/cloudinary/utils.rb, line 1407 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
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]
Private Class Methods
Source
# File lib/cloudinary/utils.rb, line 802 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
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]
Source
# File lib/cloudinary/utils.rb, line 1142 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
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
Source
# File lib/cloudinary/utils.rb, line 1170 def self.number_pattern "([0-9]*)\\.([0-9]+)|([0-9]+)" end
Source
# File lib/cloudinary/utils.rb, line 1175 def self.offset_any_pattern "(#{number_pattern})([%pP])?" end
Source
# File lib/cloudinary/utils.rb, line 1180 def self.offset_any_pattern_re /((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)\.\.((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)/ end
Source
# 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
Parse layer options @return [string] layer transformation string @private
Source
# 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
Parse radius options @return [string] radius transformation string @private
Source
# File lib/cloudinary/utils.rb, line 1225 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
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