cloudinary 1.18.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,14 @@
1
1
  class Cloudinary::Search
2
+ SORT_BY = :sort_by
3
+ AGGREGATE = :aggregate
4
+ WITH_FIELD = :with_field
5
+ KEYS_WITH_UNIQUE_VALUES = [SORT_BY, AGGREGATE, WITH_FIELD].freeze
6
+
2
7
  def initialize
3
8
  @query_hash = {
4
- :sort_by => [],
5
- :aggregate => [],
6
- :with_field => []
9
+ SORT_BY => {},
10
+ AGGREGATE => {},
11
+ WITH_FIELD => {}
7
12
  }
8
13
  end
9
14
 
@@ -28,23 +33,51 @@ class Cloudinary::Search
28
33
  self
29
34
  end
30
35
 
36
+ # Sets the `sort_by` field.
37
+ #
38
+ # @param [String] field_name The field to sort by. You can specify more than one sort_by parameter;
39
+ # results will be sorted according to the order of the fields provided.
40
+ # @param [String] dir Sort direction. Valid sort directions are 'asc' or 'desc'. Default: 'desc'.
41
+ #
42
+ # @return [Cloudinary::Search]
31
43
  def sort_by(field_name, dir = 'desc')
32
- @query_hash[:sort_by].push(field_name => dir)
44
+ @query_hash[SORT_BY][field_name] = { field_name => dir }
33
45
  self
34
46
  end
35
47
 
48
+ # The name of a field (attribute) for which an aggregation count should be calculated and returned in the response.
49
+ #
50
+ # You can specify more than one aggregate parameter.
51
+ #
52
+ # @param [String] value Supported values: resource_type, type, pixels (only the image assets in the response are
53
+ # aggregated), duration (only the video assets in the response are aggregated), format, and
54
+ # bytes. For aggregation fields without discrete values, the results are divided into
55
+ # categories.
56
+ # @return [Cloudinary::Search]
36
57
  def aggregate(value)
37
- @query_hash[:aggregate].push(value)
58
+ @query_hash[AGGREGATE][value] = value
38
59
  self
39
60
  end
40
61
 
62
+ # The name of an additional asset attribute to include for each asset in the response.
63
+ #
64
+ # @param [String] value Possible value: context, tags, and for Tier 2 also image_metadata, and image_analysis.
65
+ #
66
+ # @return [Cloudinary::Search]
41
67
  def with_field(value)
42
- @query_hash[:with_field].push(value)
68
+ @query_hash[WITH_FIELD][value] = value
43
69
  self
44
70
  end
45
71
 
72
+ # Returns the query as an hash.
73
+ #
74
+ # @return [Hash]
46
75
  def to_h
47
- @query_hash.select { |_, value| !value.nil? && !(value.is_a?(Array) && value.empty?) }
76
+ @query_hash.each_with_object({}) do |(key, value), query|
77
+ next if value.nil? || ((value.is_a?(Array) || value.is_a?(Hash)) && value.blank?)
78
+
79
+ query[key] = KEYS_WITH_UNIQUE_VALUES.include?(key) ? value.values : value
80
+ end
48
81
  end
49
82
 
50
83
  def execute(options = {})
@@ -42,6 +42,7 @@ class Cloudinary::Uploader
42
42
  :faces => Cloudinary::Utils.as_safe_bool(options[:faces]),
43
43
  :folder => options[:folder],
44
44
  :format => options[:format],
45
+ :filename_override => options[:filename_override],
45
46
  :headers => build_custom_headers(options[:headers]),
46
47
  :image_metadata => Cloudinary::Utils.as_safe_bool(options[:image_metadata]),
47
48
  :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
@@ -158,7 +159,9 @@ class Cloudinary::Uploader
158
159
  :from_public_id => from_public_id,
159
160
  :to_public_id => to_public_id,
160
161
  :to_type => options[:to_type],
161
- :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate])
162
+ :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
163
+ :context => options[:context],
164
+ :metadata => options[:metadata]
162
165
  }
163
166
  end
164
167
  end
@@ -206,17 +209,42 @@ class Cloudinary::Uploader
206
209
  end
207
210
  end
208
211
 
209
- def self.generate_sprite(tag, options={})
212
+ SLIDESHOW_PARAMS = [:notification_url, :public_id, :upload_preset]
213
+
214
+ # Creates auto-generated video slideshow.
215
+ #
216
+ # @param [Hash] options Additional options.
217
+ #
218
+ # @return [Hash] Hash with meta information URLs of generated slideshow resources.
219
+ def self.create_slideshow(options = {})
220
+ options[:resource_type] ||= :video
221
+
222
+ call_api("create_slideshow", options) do
223
+ params = {
224
+ :timestamp => Time.now.to_i,
225
+ :transformation => Cloudinary::Utils.build_eager(options[:transformation]),
226
+ :manifest_transformation => Cloudinary::Utils.build_eager(options[:manifest_transformation]),
227
+ :manifest_json => options[:manifest_json] && options[:manifest_json].to_json,
228
+ :tags => options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
229
+ :overwrite => Cloudinary::Utils.as_safe_bool(options[:overwrite])
230
+ }
231
+ SLIDESHOW_PARAMS.each { |k| params[k] = options[k] unless options[k].nil? }
232
+
233
+ params
234
+ end
235
+ end
236
+
237
+ # Generates sprites by merging multiple images into a single large image.
238
+ #
239
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
240
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
241
+ #
242
+ # @return [Hash] Hash with meta information URLs of generated sprite resources
243
+ def self.generate_sprite(tag, options = {})
210
244
  version_store = options.delete(:version_store)
211
245
 
212
246
  result = call_api("sprite", options) do
213
- {
214
- :timestamp => (options[:timestamp] || Time.now.to_i),
215
- :tag => tag,
216
- :async => options[:async],
217
- :notification_url => options[:notification_url],
218
- :transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format]))
219
- }
247
+ Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
220
248
  end
221
249
 
222
250
  if version_store == :file && result && result["version"]
@@ -228,16 +256,15 @@ class Cloudinary::Uploader
228
256
  return result
229
257
  end
230
258
 
231
- def self.multi(tag, options={})
259
+ # Creates either a single animated image, video or a PDF.
260
+ #
261
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
262
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
263
+ #
264
+ # @return [Hash] Hash with meta information URLs of the generated file
265
+ def self.multi(tag, options = {})
232
266
  call_api("multi", options) do
233
- {
234
- :timestamp => (options[:timestamp] || Time.now.to_i),
235
- :tag => tag,
236
- :format => options[:format],
237
- :async => options[:async],
238
- :notification_url => options[:notification_url],
239
- :transformation => Cloudinary::Utils.generate_transformation_string(options.clone)
240
- }
267
+ Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
241
268
  end
242
269
  end
243
270
 
@@ -341,11 +368,13 @@ class Cloudinary::Uploader
341
368
  non_signable ||= []
342
369
 
343
370
  unless options[:unsigned]
344
- api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
345
- api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
346
- params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret)
347
- params[:api_key] = api_key
371
+ api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
372
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
373
+ signature_algorithm = options[:signature_algorithm]
374
+ params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret, signature_algorithm)
375
+ params[:api_key] = api_key
348
376
  end
377
+ proxy = options[:api_proxy] || Cloudinary.config.api_proxy
349
378
  timeout = options.fetch(:timeout) { Cloudinary.config.to_h.fetch(:timeout, 60) }
350
379
 
351
380
  result = nil
@@ -355,7 +384,7 @@ class Cloudinary::Uploader
355
384
  headers['Content-Range'] = options[:content_range] if options[:content_range]
356
385
  headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
357
386
  headers.merge!(options[:extra_headers]) if options[:extra_headers]
358
- RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers) do
387
+ RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers, :proxy => proxy) do
359
388
  |response, request, tmpresult|
360
389
  raise CloudinaryException, "Server returned unexpected status code - #{response.code} - #{response.body}" unless [200, 400, 401, 403, 404, 500].include?(response.code)
361
390
  begin
@@ -13,6 +13,7 @@ require 'cloudinary/responsive'
13
13
  class Cloudinary::Utils
14
14
  # @deprecated Use Cloudinary::SHARED_CDN
15
15
  SHARED_CDN = Cloudinary::SHARED_CDN
16
+ MODE_DOWNLOAD = "download"
16
17
  DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
17
18
  CONDITIONAL_OPERATORS = {
18
19
  "=" => 'eq',
@@ -32,19 +33,34 @@ class Cloudinary::Utils
32
33
 
33
34
  PREDEFINED_VARS = {
34
35
  "aspect_ratio" => "ar",
36
+ "aspectRatio" => "ar",
35
37
  "current_page" => "cp",
38
+ "currentPage" => "cp",
36
39
  "face_count" => "fc",
40
+ "faceCount" => "fc",
37
41
  "height" => "h",
38
42
  "initial_aspect_ratio" => "iar",
43
+ "initialAspectRatio" => "iar",
44
+ "trimmed_aspect_ratio" => "tar",
45
+ "trimmedAspectRatio" => "tar",
39
46
  "initial_height" => "ih",
47
+ "initialHeight" => "ih",
40
48
  "initial_width" => "iw",
49
+ "initialWidth" => "iw",
41
50
  "page_count" => "pc",
51
+ "pageCount" => "pc",
42
52
  "page_x" => "px",
53
+ "pageX" => "px",
43
54
  "page_y" => "py",
55
+ "pageY" => "py",
44
56
  "tags" => "tags",
45
57
  "initial_duration" => "idu",
58
+ "initialDuration" => "idu",
46
59
  "duration" => "du",
47
- "width" => "w"
60
+ "width" => "w",
61
+ "illustration_score" => "ils",
62
+ "illustrationScore" => "ils",
63
+ "context" => "ctx"
48
64
  }
49
65
 
50
66
  SIMPLE_TRANSFORMATION_PARAMS = {
@@ -144,6 +160,16 @@ class Cloudinary::Utils
144
160
  LONG_URL_SIGNATURE_LENGTH = 32
145
161
  SHORT_URL_SIGNATURE_LENGTH = 8
146
162
 
163
+ UPLOAD_PREFIX = 'https://api.cloudinary.com'
164
+
165
+ ALGO_SHA1 = :sha1
166
+ ALGO_SHA256 = :sha256
167
+
168
+ ALGORITHM_SIGNATURE = {
169
+ ALGO_SHA1 => Digest::SHA1,
170
+ ALGO_SHA256 => Digest::SHA256,
171
+ }
172
+
147
173
  def self.extract_config_params(options)
148
174
  options.select{|k,v| URL_KEYS.include?(k)}
149
175
  end
@@ -309,16 +335,16 @@ class Cloudinary::Utils
309
335
  "if_" + normalize_expression(if_value) unless if_value.to_s.empty?
310
336
  end
311
337
 
312
- EXP_REGEXP = Regexp.new('(?<!\$)('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
338
+ EXP_REGEXP = Regexp.new('(\$_*[^_ ]+)|(?<![\$:])('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
313
339
  EXP_REPLACEMENT = PREDEFINED_VARS.merge(CONDITIONAL_OPERATORS)
314
340
 
315
341
  def self.normalize_expression(expression)
316
342
  if expression.nil?
317
- ''
343
+ nil
318
344
  elsif expression.is_a?( String) && expression =~ /^!.+!$/ # quoted string
319
345
  expression
320
346
  else
321
- expression.to_s.gsub(EXP_REGEXP,EXP_REPLACEMENT).gsub(/[ _]+/, "_")
347
+ expression.to_s.gsub(EXP_REGEXP) { |match| EXP_REPLACEMENT[match] || match }.gsub(/[ _]+/, "_")
322
348
  end
323
349
  end
324
350
 
@@ -405,6 +431,8 @@ class Cloudinary::Utils
405
431
  ]
406
432
 
407
433
  def self.text_style(layer)
434
+ return layer[:text_style] if layer[:text_style].present?
435
+
408
436
  font_family = layer[:font_family]
409
437
  font_size = layer[:font_size]
410
438
  keywords = []
@@ -433,9 +461,9 @@ class Cloudinary::Utils
433
461
  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("&")
434
462
  end
435
463
 
436
- def self.api_sign_request(params_to_sign, api_secret)
464
+ def self.api_sign_request(params_to_sign, api_secret, signature_algorithm = nil)
437
465
  to_sign = api_string_to_sign(params_to_sign)
438
- Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
466
+ hash("#{to_sign}#{api_secret}", signature_algorithm, :hexdigest)
439
467
  end
440
468
 
441
469
  # Returns a JSON array as String.
@@ -501,6 +529,7 @@ class Cloudinary::Utils
501
529
  use_root_path = config_option_consume(options, :use_root_path)
502
530
  auth_token = config_option_consume(options, :auth_token)
503
531
  long_url_signature = config_option_consume(options, :long_url_signature)
532
+ signature_algorithm = config_option_consume(options, :signature_algorithm)
504
533
  unless auth_token == false
505
534
  auth_token = Cloudinary::AuthToken.merge_auth_token(Cloudinary.config.auth_token, auth_token)
506
535
  end
@@ -545,7 +574,10 @@ class Cloudinary::Utils
545
574
  raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
546
575
  to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
547
576
  to_sign = fully_unescape(to_sign)
548
- signature = compute_signature(to_sign, secret, long_url_signature)
577
+ signature_algorithm = long_url_signature ? ALGO_SHA256 : signature_algorithm
578
+ hash = hash("#{to_sign}#{secret}", signature_algorithm)
579
+ signature = Base64.urlsafe_encode64(hash)
580
+ signature = "s--#{signature[0, long_url_signature ? LONG_URL_SIGNATURE_LENGTH : SHORT_URL_SIGNATURE_LENGTH ]}--"
549
581
  end
550
582
 
551
583
  prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
@@ -661,22 +693,72 @@ class Cloudinary::Utils
661
693
  prefix
662
694
  end
663
695
 
696
+ # Creates a base URL for the cloudinary api
697
+ #
698
+ # @param [Object] path Resource name
699
+ # @param [Hash] options Additional options
700
+ #
701
+ # @return [String]
702
+ def self.base_api_url(path, options = {})
703
+ cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || UPLOAD_PREFIX
704
+ cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, 'Must supply cloud_name')
705
+
706
+ [cloudinary, 'v1_1', cloud_name, path].join('/')
707
+ end
708
+
664
709
  def self.cloudinary_api_url(action = 'upload', options = {})
665
- cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
666
- cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, "Must supply cloud_name")
667
- resource_type = options[:resource_type] || "image"
668
- return [cloudinary, "v1_1", cloud_name, resource_type, action].join("/")
710
+ resource_type = options[:resource_type] || 'image'
711
+
712
+ base_api_url([resource_type, action], options)
669
713
  end
670
714
 
671
715
  def self.sign_request(params, options={})
672
716
  api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
673
717
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
718
+ signature_algorithm = options[:signature_algorithm]
674
719
  params = params.reject{|k, v| self.safe_blank?(v)}
675
- params[:signature] = Cloudinary::Utils.api_sign_request(params, api_secret)
720
+ params[:signature] = api_sign_request(params, api_secret, signature_algorithm)
676
721
  params[:api_key] = api_key
677
722
  params
678
723
  end
679
724
 
725
+ # Helper method for generating download URLs
726
+ #
727
+ # @param [String] action @see Cloudinary::Utils.cloudinary_api_url
728
+ # @param [Hash] params Query parameters in generated URL
729
+ # @param [Hash] options Additional options
730
+ # @yield [query_parameters] Invokes the block with query parameters to override how to encode them
731
+ #
732
+ # @return [String]
733
+ def self.cloudinary_api_download_url(action, params, options = {})
734
+ cloudinary_params = sign_request(params.merge(mode: MODE_DOWNLOAD), options)
735
+
736
+ "#{Cloudinary::Utils.cloudinary_api_url(action, options)}?#{hash_query_params(cloudinary_params)}"
737
+ end
738
+ private_class_method :cloudinary_api_download_url
739
+
740
+ # Return a signed URL to the 'generate_sprite' endpoint with 'mode=download'.
741
+ #
742
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
743
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
744
+ #
745
+ # @return [String] The signed URL to download sprite
746
+ def self.download_generated_sprite(tag, options = {})
747
+ params = build_multi_and_sprite_params(tag, options)
748
+ cloudinary_api_download_url("sprite", params, options)
749
+ end
750
+
751
+ # Return a signed URL to the 'multi' endpoint with 'mode=download'.
752
+ #
753
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
754
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
755
+ #
756
+ # @return [String] The signed URL to download multi
757
+ def self.download_multi(tag, options = {})
758
+ params = build_multi_and_sprite_params(tag, options)
759
+ cloudinary_api_download_url("multi", params, options)
760
+ end
761
+
680
762
  def self.private_download_url(public_id, format, options = {})
681
763
  cloudinary_params = sign_request({
682
764
  :timestamp=>Time.now.to_i,
@@ -725,11 +807,10 @@ class Cloudinary::Utils
725
807
  # @option options [String] :keep_derived (false) keep the derived images used for generating the archive
726
808
  # @return [String] archive url
727
809
  def self.download_archive_url(options = {})
728
- cloudinary_params = sign_request(Cloudinary::Utils.archive_params(options.merge(:mode => "download")), options)
729
- return Cloudinary::Utils.cloudinary_api_url("generate_archive", options) + "?" + hash_query_params(cloudinary_params)
810
+ params = Cloudinary::Utils.archive_params(options)
811
+ cloudinary_api_download_url("generate_archive", params, options)
730
812
  end
731
813
 
732
-
733
814
  # Returns a URL that when invokes creates an zip archive and returns it.
734
815
  # @see download_archive_url
735
816
  def self.download_zip_url(options = {})
@@ -1149,23 +1230,129 @@ class Cloudinary::Utils
1149
1230
  REMOTE_URL_REGEX === url
1150
1231
  end
1151
1232
 
1152
- # Computes a short or long signature based on a message and secret
1153
- # @param [String] message The string to sign
1154
- # @param [String] secret A secret that will be added to the message when signing
1155
- # @param [Boolean] long_signature Whether to create a short or long signature
1156
- # @return [String] Properly formatted signature
1157
- def self.compute_signature(message, secret, long_url_signature)
1158
- combined_message_secret = message + secret
1159
-
1160
- algo, signature_length =
1161
- if long_url_signature
1162
- [Digest::SHA256, LONG_URL_SIGNATURE_LENGTH]
1233
+ # Build params for multi, download_multi, generate_sprite, and download_generated_sprite methods
1234
+ #
1235
+ # @param [String|Hash] tag_or_options Treated as additional options when hash is passed, otherwise as a tag
1236
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
1237
+ #
1238
+ # @return [Hash]
1239
+ #
1240
+ # @private
1241
+ def self.build_multi_and_sprite_params(tag_or_options, options)
1242
+ if tag_or_options.is_a?(Hash)
1243
+ if options.blank?
1244
+ options = tag_or_options
1245
+ tag_or_options = nil
1163
1246
  else
1164
- [Digest::SHA1, SHORT_URL_SIGNATURE_LENGTH]
1247
+ raise "First argument must be a tag when additional options are passed"
1165
1248
  end
1249
+ end
1250
+ urls = options.delete(:urls)
1166
1251
 
1167
- "s--#{Base64.urlsafe_encode64(algo.digest(combined_message_secret))[0, signature_length]}--"
1252
+ if tag_or_options.blank? && urls.blank?
1253
+ raise "Either tag or urls are required"
1254
+ end
1255
+
1256
+ {
1257
+ :tag => tag_or_options,
1258
+ :urls => urls,
1259
+ :transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format])),
1260
+ :notification_url => options[:notification_url],
1261
+ :format => options[:format],
1262
+ :async => options[:async],
1263
+ :mode => options[:mode],
1264
+ :timestamp => (options[:timestamp] || Time.now.to_i)
1265
+ }
1266
+ end
1267
+
1268
+ # The returned url should allow downloading the backedup asset based on the version and asset id
1269
+ #
1270
+ # asset and version id are returned with resource(<PUBLIC_ID1>, { versions: true })
1271
+ #
1272
+ # @param [String] asset_id Asset identifier
1273
+ # @param [String] version_id Specific version of asset to download
1274
+ # @param [Hash] options Additional options
1275
+ #
1276
+ # @return [String] An url for downloading a file
1277
+ def self.download_backedup_asset(asset_id, version_id, options = {})
1278
+ params = Cloudinary::Utils.sign_request({
1279
+ :timestamp => (options[:timestamp] || Time.now.to_i),
1280
+ :asset_id => asset_id,
1281
+ :version_id => version_id
1282
+ }, options)
1283
+
1284
+ "#{Cloudinary::Utils.base_api_url("download_backup", options)}?#{Cloudinary::Utils.hash_query_params((params))}"
1285
+ end
1286
+
1287
+ # Format date in a format accepted by the usage API (e.g., 31-12-2020) if
1288
+ # passed value is of type Date, otherwise return the string representation of
1289
+ # the input.
1290
+ #
1291
+ # @param [Date|Object] date
1292
+ # @return [String]
1293
+ def self.to_usage_api_date_format(date)
1294
+ if date.is_a?(Date)
1295
+ date.strftime('%d-%m-%Y')
1296
+ else
1297
+ date.to_s
1298
+ end
1299
+ end
1300
+
1301
+ # Verifies the authenticity of an API response signature.
1302
+ #
1303
+ # @param [String] public_id he public id of the asset as returned in the API response
1304
+ # @param [Fixnum] version The version of the asset as returned in the API response
1305
+ # @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
1306
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1307
+ # @param [Hash] options
1308
+ # @option options [String] :api_secret API secret, if not passed taken from global config
1309
+ #
1310
+ # @return [Boolean]
1311
+ def self.verify_api_response_signature(public_id, version, signature, signature_algorithm = nil, options = {})
1312
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
1313
+
1314
+ parameters_to_sign = {
1315
+ :public_id => public_id,
1316
+ :version => version
1317
+ }
1318
+
1319
+ signature == api_sign_request(parameters_to_sign, api_secret, signature_algorithm)
1320
+ end
1321
+
1322
+ # Verifies the authenticity of a notification signature.
1323
+ #
1324
+ # @param [String] body JSON of the request's body
1325
+ # @param [Fixnum] timestamp Unix timestamp. Can be retrieved from the X-Cld-Timestamp header
1326
+ # @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
1327
+ # @param [Fixnum] valid_for The desired time in seconds for considering the request valid
1328
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1329
+ # @param [Hash] options
1330
+ # @option options [String] :api_secret API secret, if not passed taken from global config
1331
+ #
1332
+ # @return [Boolean]
1333
+ def self.verify_notification_signature(body, timestamp, signature, valid_for = 7200, signature_algorithm = nil, options = {})
1334
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
1335
+ raise("Body should be of String type") unless body.is_a?(String)
1336
+ # verify that signature is valid for the given timestamp
1337
+ return false if timestamp < (Time.now - valid_for).to_i
1338
+
1339
+ payload_hash = hash("#{body}#{timestamp}#{api_secret}", signature_algorithm, :hexdigest)
1340
+
1341
+ signature == payload_hash
1342
+ end
1343
+
1344
+ # Computes hash from input string using specified algorithm.
1345
+ #
1346
+ # @param [String] input String which to compute hash from
1347
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1348
+ # @param [Symbol] hash_method Hash method applied to a signature algorithm (:digest or :hexdigest)
1349
+ #
1350
+ # @return [String] Computed hash value
1351
+ def self.hash(input, signature_algorithm = nil, hash_method = :digest)
1352
+ signature_algorithm ||= Cloudinary.config.signature_algorithm || ALGO_SHA1
1353
+ algorithm = ALGORITHM_SIGNATURE[signature_algorithm] || raise("Unsupported algorithm '#{signature_algorithm}'")
1354
+ algorithm.public_send(hash_method, input)
1168
1355
  end
1169
1356
 
1170
- private_class_method :compute_signature
1357
+ private_class_method :hash
1171
1358
  end