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
# 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
# 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
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
# 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
# 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
# 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
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
# 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
# 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
@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
# 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
# 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
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
# 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
# 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
# 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
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
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
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
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
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
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
# 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
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
# File lib/cloudinary/utils.rb, line 171 def self.extract_config_params(options) options.select{|k,v| URL_KEYS.include?(k)} end
# File lib/cloudinary/utils.rb, line 175 def self.extract_transformation_params(options) options.select{|k,v| TRANSFORMATION_PARAMS.include?(k)} end
# 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
# 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
# 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
# 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
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
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
# 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
# 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
# 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
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
# 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
# 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
# File lib/cloudinary/utils.rb, line 1001 def self.safe_blank?(value) value.nil? || value == "" || value == [] end
# 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
# 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
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
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
# 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
# File lib/cloudinary/utils.rb, line 947 def self.supported_image_format?(format) supported_format? format, IMAGE_FORMATS end
# 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
# 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
# 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
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
# 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
Private Class Methods
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
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
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
# 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
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
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
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
# 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
# File lib/cloudinary/utils.rb, line 1239 def self.is_remote?(url) REMOTE_URL_REGEX === url end
# File lib/cloudinary/utils.rb, line 1131 def self.number_pattern "([0-9]*)\\.([0-9]+)|([0-9]+)" end
# File lib/cloudinary/utils.rb, line 1136 def self.offset_any_pattern "(#{number_pattern})([%pP])?" end
# 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
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
# 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
# File lib/cloudinary/utils.rb, line 1211 def self.process_custom_pre_function(param) value = process_custom_function(param) value ? "pre:#{value}" : nil end
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
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
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
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
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
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