Class: Cloudinary::Utils

Inherits:
Object show all
Defined in:
lib/cloudinary/utils.rb

Constant Summary collapse

SHARED_CDN =
Deprecated.

Use Cloudinary::SHARED_CDN

Cloudinary::SHARED_CDN
IMAGE_FORMATS =
%w(bmp png tif tiff jpg jpeg gif pdf ico eps)
@@json_decode =
false

Class Method Summary collapse

Class Method Details

.api_sign_request(params_to_sign, api_secret) ⇒ Object



79
80
81
82
# File 'lib/cloudinary/utils.rb', line 79

def self.api_sign_request(params_to_sign, api_secret)
  to_sign = api_string_to_sign(params_to_sign)
  Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
end

.api_string_to_sign(params_to_sign) ⇒ Object



75
76
77
# File 'lib/cloudinary/utils.rb', line 75

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

.as_bool(value) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/cloudinary/utils.rb', line 283

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 Fixnum then value != 0
  when Symbol then value == :true
  else
    raise "Invalid boolean value #{value} of type #{value.class}"
  end
end

.as_safe_bool(value) ⇒ Object



296
297
298
299
300
301
302
# File 'lib/cloudinary/utils.rb', line 296

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) ⇒ Object



217
218
219
220
221
222
223
# File 'lib/cloudinary/utils.rb', line 217

def self.asset_file_name(path)
  data = Rails.root.join(path).read(:mode=>"rb")
  ext = path.extname
  md5 = Digest::MD5.hexdigest(data)
  public_id = "#{path.basename(ext)}-#{md5}"
  "#{public_id}#{ext}"    
end

.build_array(array) ⇒ Object



258
259
260
261
262
263
264
# File 'lib/cloudinary/utils.rb', line 258

def self.build_array(array)
  case array
    when Array then array
    when nil then []
    else [array]
  end
end

.cloudinary_api_url(action = 'upload', options = {}) ⇒ Object



165
166
167
168
169
170
# File 'lib/cloudinary/utils.rb', line 165

def self.cloudinary_api_url(action = 'upload', options = {})
  cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
  cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, "Must supply cloud_name")
  resource_type = options[:resource_type] || "image"
  return [cloudinary, "v1_1", cloud_name, resource_type, action].join("/")
end

.cloudinary_url(public_id, options = {}) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/cloudinary/utils.rb', line 208

def self.cloudinary_url(public_id, options = {})
  if options[:type].to_s == 'authenticated'
    result = signed_download_url(public_id, options)
  else
    result = unsigned_download_url(public_id, options)
  end
  return result
end

.config_option_consume(options, option_name, default_value = nil) ⇒ Object



278
279
280
281
# File 'lib/cloudinary/utils.rb', line 278

def self.config_option_consume(options, option_name, default_value = nil)    
  return options.delete(option_name) if options.include?(option_name)
  return Cloudinary.config.send(option_name) || default_value    
end

.generate_transformation_string(options = {}) ⇒ Object

Warning: options are being destructively updated!



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/cloudinary/utils.rb', line 11

def self.generate_transformation_string(options={})
  if options.is_a?(Array)
    return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
  end
  # Symbolize keys

  options.keys.each do |key|
    options[key.to_sym] = options.delete(key) if key.is_a?(String)
  end
  
  size = options.delete(:size)
  options[:width], options[:height] = size.split("x") if size    
  width = options[:width]
  height = options[:height]
  has_layer = !options[:overlay].blank? || !options[:underlay].blank?
       
  crop = options.delete(:crop)
  angle = build_array(options.delete(:angle)).join(".")

  no_html_sizes = has_layer || !angle.blank? || crop.to_s == "fit" || crop.to_s == "limit"
  options.delete(:width) if width && (width.to_f < 1 || no_html_sizes)
  options.delete(:height) if height && (height.to_f < 1 || no_html_sizes)

  width=height=nil if crop.nil? && !has_layer

  background = options.delete(:background)
  background = background.sub(/^#/, 'rgb:') if background
      
  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) : generate_transformation_string(:transformation=>base_transformation)
    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(".")
  
  params = {:w=>width, :h=>height, :t=>named_transformation, :c=>crop, :b=>background, :e=>effect, :a=>angle, :bo=>border, :fl=>flags}
  { :x=>:x, :y=>:y, :r=>:radius, :d=>:default_image, :g=>:gravity, :q=>:quality, :cs=>:color_space, :o=>:opacity,
    :p=>:prefix, :l=>:overlay, :u=>:underlay, :f=>:fetch_format, :dn=>:density, :pg=>:page, :dl=>:delay
  }.each do
    |param, option|
    params[param] = options.delete(option)
  end    

  transformation = params.reject{|k,v| v.blank?}.map{|k,v| [k.to_s, v]}.sort_by(&:first).map{|k,v| "#{k}_#{v}"}.join(",")
  raw_transformation = options.delete(:raw_transformation)
  transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
  (base_transformations << transformation).reject(&:blank?).join("/")    
end

.json_decode(str) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/cloudinary/utils.rb', line 242

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

.private_download_url(public_id, format, options = {}) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/cloudinary/utils.rb', line 172

def self.private_download_url(public_id, format, 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")
  cloudinary_params = {
    :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
  }.reject{|k, v| v.blank?}
  cloudinary_params[:signature] = Cloudinary::Utils.api_sign_request(cloudinary_params, api_secret)
  cloudinary_params[:api_key] = api_key
  return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + cloudinary_params.to_query 
end

.random_public_idObject



232
233
234
235
# File 'lib/cloudinary/utils.rb', line 232

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) ⇒ Object



274
275
276
# File 'lib/cloudinary/utils.rb', line 274

def self.resource_type_for_format(format)
  self.supported_image_format?(format) ? 'image' : 'raw'
end

.signed_download_url(public_id, options = {}) ⇒ Object



197
198
199
200
201
202
203
204
205
206
# File 'lib/cloudinary/utils.rb', line 197

def self.signed_download_url(public_id, options = {})
  aws_private_key_path = options[:aws_private_key_path] || Cloudinary.config.aws_private_key_path || raise(CloudinaryException, "Must supply aws_private_key_path")
  aws_key_pair_id = options[:aws_key_pair_id] || Cloudinary.config.aws_key_pair_id || raise(CloudinaryException, "Must supply aws_key_pair_id")
  authenticated_distribution = options[:authenticated_distribution] || Cloudinary.config.authenticated_distribution || raise(CloudinaryException, "Must supply authenticated_distribution")
  @signers ||= Hash.new{|h,k| path, id = k; h[k] = AwsCfSigner.new(path, id)}
  signer = @signers[[aws_private_key_path, aws_key_pair_id]]
  url = Cloudinary::Utils.unsigned_download_url(public_id, {:type=>:authenticated}.merge(options).merge(:secure=>true, :secure_distribution=>authenticated_distribution, :private_cdn=>true))
  expires_at = options[:expires_at] || (Time.now+3600)
  signer.sign(url, :ending => expires_at)
end

.signed_preloaded_image(result) ⇒ Object



237
238
239
# File 'lib/cloudinary/utils.rb', line 237

def self.signed_preloaded_image(result)
  "#{result["resource_type"]}/upload/v#{result["version"]}/#{[result["public_id"], result["format"]].reject(&:blank?).join(".")}##{result["signature"]}"
end

.smart_escape(string) ⇒ Object

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



226
227
228
229
230
# File 'lib/cloudinary/utils.rb', line 226

def self.smart_escape(string)
  string.gsub(/([^ a-zA-Z0-9_.-\/:]+)/) do
    '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
  end.tr(' ', '+')
end

.supported_image_format?(format) ⇒ Boolean

Returns:

  • (Boolean)


268
269
270
271
272
# File 'lib/cloudinary/utils.rb', line 268

def self.supported_image_format?(format)
  format = format.to_s.downcase
  extension = format =~ /\./ ? format.split('.').last : format
  IMAGE_FORMATS.include?(extension)
end

.unsigned_download_url(source, options = {}) ⇒ Object

Warning: options are being destructively updated!



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/cloudinary/utils.rb', line 85

def self.unsigned_download_url(source, options = {})

  type = options.delete(:type)

  options[:fetch_format] ||= options.delete(:format) if type == :fetch 
  transformation = self.generate_transformation_string(options)

  resource_type = options.delete(:resource_type) || "image"
  version = options.delete(:version)
  format = options.delete(:format)
  cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
          
  secure = options.delete(:secure)
  ssl_detected = options.delete(:ssl_detected)
  secure = ssl_detected || Cloudinary.config.secure if secure.nil?
  private_cdn = config_option_consume(options, :private_cdn) 
  secure_distribution = config_option_consume(options, :secure_distribution) 
  cname = config_option_consume(options, :cname) 
  shorten = config_option_consume(options, :shorten) 
  force_remote = options.delete(:force_remote)
  cdn_subdomain = config_option_consume(options, :cdn_subdomain) 
  
  original_source = source
  return original_source if source.blank?
  if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
    source = format.blank? ? source.filename : source.full_public_id 
  end
  source = source.to_s
  if !force_remote    
    return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
    if source.start_with?("/") 
      if source.start_with?("/images/")
        source = source.sub(%r(/images/), '')
      else
        return original_source
      end
    end 
     ||= defined?(Cloudinary::Static) ? Cloudinary::Static. : {}
    if type == :asset && ["images/#{source}"]
      return original_source if !Cloudinary.config.static_image_support        
      source = ["images/#{source}"]["public_id"]
      source += File.extname(original_source) if !format
    elsif type == :asset
      return original_source # requested asset, but no metadata - probably local file. return.

    end
  end
  
  type ||= :upload

  if source.match(%r(^https?:/)i)
    source = smart_escape(source)
  elsif !format.blank? 
    source = "#{source}.#{format}"
  end

  if cloud_name.start_with?("/")
    prefix = "/res" + cloud_name
  else
    secure_distribution ||= Cloudinary::SHARED_CDN
          
    if secure
      prefix = "https://#{secure_distribution}"
    else
      subdomain = cdn_subdomain ? "a#{(Zlib::crc32(source) % 5) + 1}." : ""
      host = cname.blank? ? "#{private_cdn ? "#{cloud_name}-" : ""}res.cloudinary.com" : cname
      prefix = "http://#{subdomain}#{host}"
    end    
    prefix += "/#{cloud_name}" if !private_cdn || (secure && secure_distribution == Cloudinary::AKAMAI_SHARED_CDN)
  end
  
  if shorten && resource_type.to_s == "image" && type.to_s == "upload"
    resource_type = "iu"
    type = nil
  end
  version ||= 1 if source.include?("/") and !source.match(/^v[0-9]+/) and !source.match(/^https?:\//)
  source = prefix + "/" + [resource_type, 
   type, transformation, version ? "v#{version}" : nil,
   source].reject(&:blank?).join("/").gsub(%r(([^:])//), '\1/')
end

.zip_download_url(tag, options = {}) ⇒ Object



188
189
190
191
192
193
194
195
# File 'lib/cloudinary/utils.rb', line 188

def self.zip_download_url(tag, 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")
  cloudinary_params = {:timestamp=>Time.now.to_i, :tag=>tag, :transformation=>generate_transformation_string(options)}.reject{|k, v| v.blank?}
  cloudinary_params[:signature] = Cloudinary::Utils.api_sign_request(cloudinary_params, api_secret)
  cloudinary_params[:api_key] = api_key
  return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + cloudinary_params.to_query 
end