cloudinary 1.18.0 → 1.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +15 -14
- data/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- data/.github/pull_request_template.md +18 -11
- data/CHANGELOG.md +69 -0
- data/cloudinary.gemspec +8 -3
- data/lib/active_storage/service/cloudinary_service.rb +23 -3
- data/lib/cloudinary.rb +54 -64
- data/lib/cloudinary/account_api.rb +226 -0
- data/lib/cloudinary/account_config.rb +30 -0
- data/lib/cloudinary/api.rb +65 -78
- data/lib/cloudinary/auth_token.rb +4 -0
- data/lib/cloudinary/base_api.rb +79 -0
- data/lib/cloudinary/base_config.rb +70 -0
- data/lib/cloudinary/config.rb +43 -0
- data/lib/cloudinary/search.rb +40 -7
- data/lib/cloudinary/uploader.rb +52 -23
- data/lib/cloudinary/utils.rb +216 -29
- data/lib/cloudinary/version.rb +1 -1
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +16 -4
- metadata +17 -12
data/lib/cloudinary/search.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
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[
|
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[
|
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[
|
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.
|
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 = {})
|
data/lib/cloudinary/uploader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
345
|
-
api_secret
|
346
|
-
|
347
|
-
params[:
|
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
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -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('(
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
666
|
-
|
667
|
-
resource_type
|
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] =
|
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
|
-
|
729
|
-
|
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
|
-
#
|
1153
|
-
#
|
1154
|
-
# @param [String]
|
1155
|
-
# @param [
|
1156
|
-
#
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
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
|
-
|
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
|
-
|
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 :
|
1357
|
+
private_class_method :hash
|
1171
1358
|
end
|