cloudinary 1.0.85 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.gitignore +5 -5
- data/CHANGELOG.md +13 -0
- data/README.md +8 -5
- data/Rakefile +1 -1
- data/cloudinary.gemspec +1 -1
- data/lib/cloudinary/carrier_wave.rb +18 -4
- data/lib/cloudinary/carrier_wave/process.rb +1 -1
- data/lib/cloudinary/carrier_wave/storage.rb +11 -17
- data/lib/cloudinary/utils.rb +554 -551
- data/lib/cloudinary/version.rb +1 -1
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +149 -46
- metadata +21 -21
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MjhkM2NkNzk1YTExOWQ0ZjdjMTJlNWIzOTE2YzZhMjE2NWZhNDM5OQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed522de3b73b3847f23d46ee3ec165e7cdf20037
|
4
|
+
data.tar.gz: 2bda2d2079cf979dd6622e23ec4187dbcf56be34
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
OWIyZmY1MDI4NzlhODIzZmFmNWJkZDQyYTAyOWZmNzc0MzFjMWRlZDUyZGY5
|
11
|
-
ZmYwYjQ5ODFiODM0Y2VlNjViN2JjOTJmODI2ODBiNzk4NWViMjk=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NTE4NDkyMGU0ZDk3MDNiNjA0OWMwZDZjNTU1OThjNjI3OTQ3Zjg1ZTkxM2U2
|
14
|
-
MGFjODMxOTBiNDgyYmMyYTdmZmM1Y2FkMWM0ZGZmOWI2MjM3NjUzY2E4ZDhk
|
15
|
-
YTc5OGY1OWIzNTIzYjcwYzRmNmMzZThiZjcwMTI4MTQyOTU3NmY=
|
6
|
+
metadata.gz: d75f033f6b0a02263f626b9f2f2c57de96fcf773eb455079fb647bf50c447f8280138eeb93298ba67a4d22f34c42f1f5ca29af1eb108dbe303fc19efd93b90ac
|
7
|
+
data.tar.gz: c815b8e4af15df396cc425ab505608c577c7f2616d76101041bf9c41f1561aea69fd7676a14294ef7df418d86c2375e044db6c7e6630b4d95b48c5f300e8fa30
|
data/.gitignore
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
/.config
|
4
4
|
capybara-*.html
|
5
5
|
.rspec
|
6
|
-
log
|
7
|
-
db/*.sqlite3
|
8
|
-
db/*.sqlite3-journal
|
6
|
+
/log
|
7
|
+
/db/*.sqlite3
|
8
|
+
/db/*.sqlite3-journal
|
9
9
|
/public/system
|
10
10
|
/coverage/
|
11
11
|
/InstalledFiles
|
@@ -14,7 +14,7 @@ db/*.sqlite3-journal
|
|
14
14
|
/test/tmp/
|
15
15
|
/spec/tmp
|
16
16
|
/test/version_tmp/
|
17
|
-
tmp
|
17
|
+
/tmp
|
18
18
|
**.orig
|
19
19
|
rerun.txt
|
20
20
|
pickle-email-*.html
|
@@ -49,7 +49,7 @@ Gemfile.lock
|
|
49
49
|
.rvmrc
|
50
50
|
|
51
51
|
# if using bower-rails ignore default bower_components path bower.json files
|
52
|
-
vendor/assets/bower_components
|
52
|
+
/vendor/assets/bower_components
|
53
53
|
*.bowerrc
|
54
54
|
bower.json
|
55
55
|
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
# Version 1.1.0 - 2015-04-21
|
2
|
+
* Pull request #136 - Update `process.rb` to ensure name value is an array.
|
3
|
+
* CarrierWave
|
4
|
+
* Store `resource_type` and `type` (aka `storage_type`) in carrierwave column for better support of non-image resource types and non-upload types
|
5
|
+
* only pass format to Cloudinary when explicitly requested
|
6
|
+
* Support disabling new extended identifier format
|
7
|
+
* Use upload endpoint instead of upload_chunked
|
8
|
+
* Remove `symoblize_keys` monkey patching
|
9
|
+
* Update Rspec dependency.
|
10
|
+
* Fix markup in the readme file.
|
11
|
+
* Add `.gitignore` to each sample project. Add files required for testing.
|
12
|
+
* Fix changelog format (missing newline)
|
13
|
+
|
1
14
|
# Version 1.0.85 - 2015-04-08
|
2
15
|
* Remove symoblize_keys intrusive implementation.
|
3
16
|
* Use upload API endpoint instead of upload_chunked.
|
data/README.md
CHANGED
@@ -12,13 +12,13 @@ Cloudinary provides URL and HTTP based APIs that can be easily integrated with a
|
|
12
12
|
For Ruby on Rails, Cloudinary provides a GEM for simplifying the integration even further.
|
13
13
|
|
14
14
|
## Getting started guide
|
15
|
-
![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **Take a look at our [Getting started guide of Ruby on Rails](http://cloudinary.com/documentation/rails_integration#getting_started_guide)**.
|
15
|
+
![More](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **Take a look at our [Getting started guide of Ruby on Rails](http://cloudinary.com/documentation/rails_integration#getting_started_guide)**.
|
16
16
|
|
17
17
|
## Setup ######################################################################
|
18
18
|
|
19
19
|
To install the Cloudinary Ruby GEM, run:
|
20
20
|
|
21
|
-
|
21
|
+
$ gem install cloudinary
|
22
22
|
|
23
23
|
If you use Rails 3.x or higher, edit your `Gemfile`, add the following line and run `bundle install`
|
24
24
|
|
@@ -117,7 +117,7 @@ Same goes for Twitter:
|
|
117
117
|
|
118
118
|
twitter_name_profile_image_tag("billclinton.jpg")
|
119
119
|
|
120
|
-
![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/rails_image_manipulation) for more information about displaying and transforming images in Rails**.
|
120
|
+
![More](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/rails_image_manipulation) for more information about displaying and transforming images in Rails**.
|
121
121
|
|
122
122
|
|
123
123
|
|
@@ -144,11 +144,14 @@ You can also specify your own public ID:
|
|
144
144
|
http://res.cloudinary.com/demo/image/upload/sample_remote.jpg
|
145
145
|
|
146
146
|
|
147
|
-
![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/rails_image_upload) for plenty more options of uploading to the cloud from your Ruby code or directly from the browser**.
|
147
|
+
![More](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/rails_image_upload) for plenty more options of uploading to the cloud from your Ruby code or directly from the browser**.
|
148
148
|
|
149
149
|
|
150
150
|
### CarrierWave Integration
|
151
151
|
|
152
|
+
**Note:** Starting from version 1.1.0 the CarrierWave database format has changed to include the resource type and storage type. The new functionality
|
153
|
+
is backward compatible with the previous format. To use the old format override `use_extended_identifier?` in the Uploader and return `false`.
|
154
|
+
|
152
155
|
Cloudinary's Ruby GEM includes an optional plugin for [CarrierWave](https://github.com/jnicklas/carrierwave). If you already use CarrierWave, simply include `Cloudinary::CarrierWave` to switch to cloud storage and image processing in the cloud.
|
153
156
|
|
154
157
|
class PictureUploader < CarrierWave::Uploader::Base
|
@@ -156,7 +159,7 @@ Cloudinary's Ruby GEM includes an optional plugin for [CarrierWave](https://gith
|
|
156
159
|
...
|
157
160
|
end
|
158
161
|
|
159
|
-
![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **For more details on CarrierWave integration see [our documentation](http://cloudinary.com/documentation/rails_carrierwave)**.
|
162
|
+
![More](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **For more details on CarrierWave integration see [our documentation](http://cloudinary.com/documentation/rails_carrierwave)**.
|
160
163
|
|
161
164
|
We also published an interesting blog post about [Ruby on Rails image uploads with CarrierWave and Cloudinary](http://cloudinary.com/blog/ruby_on_rails_image_uploads_with_carrierwave_and_cloudinary).
|
162
165
|
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ task :default => :spec
|
|
7
7
|
Bundler::GemHelper.install_tasks
|
8
8
|
|
9
9
|
task :fetch_assets do
|
10
|
-
system "/bin/rm -rf vendor/assets; mkdir -p vendor/assets; cd vendor/assets; curl -L https://github.com/cloudinary/cloudinary_js/
|
10
|
+
system "/bin/rm -rf vendor/assets; mkdir -p vendor/assets; cd vendor/assets; curl -L https://github.com/cloudinary/cloudinary_js/tarball/master | tar zxvf - --strip=1"
|
11
11
|
system "mkdir -p vendor/assets/javascripts; mv vendor/assets/js vendor/assets/javascripts/cloudinary"
|
12
12
|
File.open("vendor/assets/javascripts/cloudinary/index.js", "w") do
|
13
13
|
|f|
|
data/cloudinary.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
23
|
s.add_dependency "aws_cf_signer"
|
24
|
-
s.add_development_dependency "rspec", '>=2
|
24
|
+
s.add_development_dependency "rspec", '>=3.2'
|
25
25
|
s.add_development_dependency "rspec-rails"
|
26
26
|
|
27
27
|
if RUBY_VERSION > "1.9"
|
@@ -135,6 +135,11 @@ module Cloudinary::CarrierWave
|
|
135
135
|
def auto_rename_preloaded?
|
136
136
|
true
|
137
137
|
end
|
138
|
+
|
139
|
+
# Use extended identifier format that includes resource type and storage type.
|
140
|
+
def use_extended_identifier?
|
141
|
+
true
|
142
|
+
end
|
138
143
|
|
139
144
|
class CloudinaryFile
|
140
145
|
attr_reader :identifier, :public_id, :filename, :format, :version, :storage_type, :resource_type
|
@@ -142,7 +147,12 @@ module Cloudinary::CarrierWave
|
|
142
147
|
@uploader = uploader
|
143
148
|
@identifier = identifier
|
144
149
|
|
145
|
-
if @identifier.match(%r(^v([0-9]+)
|
150
|
+
if @identifier.match(%r(^(image|raw|video)/(upload|private|authenticated)(?:/v([0-9]+))?/(.*)))
|
151
|
+
@resource_type = $1
|
152
|
+
@storage_type = $2
|
153
|
+
@version = $3.presence
|
154
|
+
@filename = $4
|
155
|
+
elsif @identifier.match(%r(^v([0-9]+)/(.*)))
|
146
156
|
@version = $1
|
147
157
|
@filename = $2
|
148
158
|
else
|
@@ -150,8 +160,8 @@ module Cloudinary::CarrierWave
|
|
150
160
|
@version = nil
|
151
161
|
end
|
152
162
|
|
153
|
-
@storage_type
|
154
|
-
@resource_type
|
163
|
+
@storage_type ||= uploader.class.storage_type
|
164
|
+
@resource_type ||= Cloudinary::Utils.resource_type_for_format(@filename)
|
155
165
|
@public_id, @format = Cloudinary::PreloadedFile.split_format(@filename)
|
156
166
|
end
|
157
167
|
|
@@ -180,8 +190,12 @@ module Cloudinary::CarrierWave
|
|
180
190
|
"png"
|
181
191
|
end
|
182
192
|
|
193
|
+
def storage_type
|
194
|
+
@file.respond_to?(:storage_type) ? @file.storage_type : self.class.storage_type
|
195
|
+
end
|
196
|
+
|
183
197
|
def resource_type
|
184
|
-
Cloudinary::Utils.resource_type_for_format(requested_format || original_filename || default_format)
|
198
|
+
@file.respond_to?(:resource_type) ? @file.resource_type : Cloudinary::Utils.resource_type_for_format(requested_format || original_filename || default_format)
|
185
199
|
end
|
186
200
|
|
187
201
|
# For the given methods - versions should call the main uploader method
|
@@ -11,11 +11,11 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
11
11
|
@stored_version = file.version
|
12
12
|
uploader.rename(nil, true)
|
13
13
|
else
|
14
|
-
store_cloudinary_identifier(file.version, file.filename)
|
14
|
+
store_cloudinary_identifier(file.version, file.filename, file.resource_type, file.type)
|
15
15
|
end
|
16
|
-
return
|
16
|
+
return # Nothing to do
|
17
17
|
when Cloudinary::CarrierWave::CloudinaryFile, Cloudinary::CarrierWave::StoredFile
|
18
|
-
return
|
18
|
+
return # Nothing to do
|
19
19
|
when Cloudinary::CarrierWave::RemoteFile
|
20
20
|
data = file.uri.to_s
|
21
21
|
else
|
@@ -26,7 +26,7 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
26
26
|
# This is the toplevel, need to upload the actual file.
|
27
27
|
params = uploader.transformation.dup
|
28
28
|
params[:return_error] = true
|
29
|
-
params[:format] = uploader.
|
29
|
+
params[:format] = uploader.requested_format
|
30
30
|
params[:public_id] = uploader.my_public_id
|
31
31
|
uploader.versions.values.each(&:tags) # Validate no tags in versions
|
32
32
|
params[:tags] = uploader.tags if uploader.tags
|
@@ -42,7 +42,7 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
42
42
|
|
43
43
|
if uploader.metadata["version"]
|
44
44
|
filename = [uploader.metadata["public_id"], uploader.metadata["format"]].reject(&:blank?).join(".")
|
45
|
-
store_cloudinary_identifier(uploader.metadata["version"], filename)
|
45
|
+
store_cloudinary_identifier(uploader.metadata["version"], filename, uploader.metadata["resource_type"], uploader.metadata["type"])
|
46
46
|
end
|
47
47
|
# Will throw an exception on error
|
48
48
|
else
|
@@ -52,24 +52,18 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
52
52
|
nil
|
53
53
|
end
|
54
54
|
|
55
|
-
# @deprecated For backward compatibility
|
56
|
-
def store_cloudinary_version(version)
|
57
|
-
if identifier.match(%r(^(v[0-9]+)/(.*)))
|
58
|
-
filename = $2
|
59
|
-
else
|
60
|
-
filename = identifier
|
61
|
-
end
|
62
|
-
|
63
|
-
store_cloudinary_identifier(version, filename)
|
64
|
-
end
|
65
|
-
|
66
55
|
# Updates the model mounter identifier with version information.
|
67
56
|
#
|
68
57
|
# Carrierwave uses hooks when integrating with ORMs so it's important to
|
69
58
|
# update the identifier in a way that does not trigger hooks again or else
|
70
59
|
# you'll get stuck in a loop.
|
71
|
-
def store_cloudinary_identifier(version, filename)
|
60
|
+
def store_cloudinary_identifier(version, filename, resource_type=nil, type=nil)
|
72
61
|
name = "v#{version}/#{filename}"
|
62
|
+
if uploader.use_extended_identifier?
|
63
|
+
resource_type ||= uploader.resource_type || "image"
|
64
|
+
type ||= uploader.storage_type || "upload"
|
65
|
+
name = "#{resource_type}/#{type}/#{name}"
|
66
|
+
end
|
73
67
|
model_class = uploader.model.class
|
74
68
|
column = uploader.model.send(:_mounter, uploader.mounted_as).send(:serialization_column)
|
75
69
|
if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -1,551 +1,554 @@
|
|
1
|
-
# Copyright Cloudinary
|
2
|
-
require 'digest/sha1'
|
3
|
-
require 'zlib'
|
4
|
-
require 'uri'
|
5
|
-
require 'aws_cf_signer'
|
6
|
-
|
7
|
-
class Cloudinary::Utils
|
8
|
-
# @deprecated Use Cloudinary::SHARED_CDN
|
9
|
-
SHARED_CDN = Cloudinary::SHARED_CDN
|
10
|
-
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
|
11
|
-
|
12
|
-
# Warning: options are being destructively updated!
|
13
|
-
def self.generate_transformation_string(options={})
|
14
|
-
if options.is_a?(Array)
|
15
|
-
return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
|
16
|
-
end
|
17
|
-
# Symbolize keys
|
18
|
-
options.keys.each do |key|
|
19
|
-
options[(key.to_sym rescue key)] = options.delete(key)
|
20
|
-
end
|
21
|
-
|
22
|
-
responsive_width = config_option_consume(options, :responsive_width)
|
23
|
-
size = options.delete(:size)
|
24
|
-
options[:width], options[:height] = size.split("x") if size
|
25
|
-
width = options[:width]
|
26
|
-
width = width.to_s if width.is_a?(Symbol)
|
27
|
-
height = options[:height]
|
28
|
-
has_layer = options[:overlay].present? || options[:underlay].present?
|
29
|
-
|
30
|
-
crop = options.delete(:crop)
|
31
|
-
angle = build_array(options.delete(:angle)).join(".")
|
32
|
-
|
33
|
-
no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
|
34
|
-
options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width == "auto" || responsive_width)
|
35
|
-
options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)
|
36
|
-
|
37
|
-
width=height=nil if crop.nil? && !has_layer && width != "auto"
|
38
|
-
|
39
|
-
background = options.delete(:background)
|
40
|
-
background = background.sub(/^#/, 'rgb:') if background
|
41
|
-
|
42
|
-
color = options.delete(:color)
|
43
|
-
color = color.sub(/^#/, 'rgb:') if color
|
44
|
-
|
45
|
-
base_transformations = build_array(options.delete(:transformation))
|
46
|
-
if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
|
47
|
-
base_transformations = base_transformations.map do
|
48
|
-
|base_transformation|
|
49
|
-
base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone) : generate_transformation_string(:transformation=>base_transformation)
|
50
|
-
end
|
51
|
-
else
|
52
|
-
named_transformation = base_transformations.join(".")
|
53
|
-
base_transformations = []
|
54
|
-
end
|
55
|
-
|
56
|
-
effect = options.delete(:effect)
|
57
|
-
effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)
|
58
|
-
|
59
|
-
border = options.delete(:border)
|
60
|
-
if border.is_a?(Hash)
|
61
|
-
border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
|
62
|
-
elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
|
63
|
-
options[:border] = border
|
64
|
-
border = nil
|
65
|
-
end
|
66
|
-
flags = build_array(options.delete(:flags)).join(".")
|
67
|
-
dpr = config_option_consume(options, :dpr)
|
68
|
-
|
69
|
-
if options.include? :offset
|
70
|
-
options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
|
71
|
-
end
|
72
|
-
|
73
|
-
params = {
|
74
|
-
:a => angle,
|
75
|
-
:b => background,
|
76
|
-
:bo => border,
|
77
|
-
:c => crop,
|
78
|
-
:co => color,
|
79
|
-
:dpr => dpr,
|
80
|
-
:e => effect,
|
81
|
-
:fl => flags,
|
82
|
-
:h => height,
|
83
|
-
:t => named_transformation,
|
84
|
-
:w => width
|
85
|
-
}
|
86
|
-
{
|
87
|
-
:ac => :audio_codec,
|
88
|
-
:br => :bit_rate,
|
89
|
-
:cs => :color_space,
|
90
|
-
:d => :default_image,
|
91
|
-
:dl => :delay,
|
92
|
-
:dn => :density,
|
93
|
-
:du => :duration,
|
94
|
-
:eo => :end_offset,
|
95
|
-
:f => :fetch_format,
|
96
|
-
:g => :gravity,
|
97
|
-
:l => :overlay,
|
98
|
-
:o => :opacity,
|
99
|
-
:p => :prefix,
|
100
|
-
:pg => :page,
|
101
|
-
:q => :quality,
|
102
|
-
:r => :radius,
|
103
|
-
:af => :audio_frequency,
|
104
|
-
:so => :start_offset,
|
105
|
-
:u => :underlay,
|
106
|
-
:vc => :video_codec,
|
107
|
-
:vs => :video_sampling,
|
108
|
-
:x => :x,
|
109
|
-
:y => :y,
|
110
|
-
:z => :zoom
|
111
|
-
}.each do
|
112
|
-
|param, option|
|
113
|
-
params[param] = options.delete(option)
|
114
|
-
end
|
115
|
-
|
116
|
-
params[:vc] = process_video_params params[:vc] if params[:vc].present?
|
117
|
-
[:so, :eo, :du].each do |range_value|
|
118
|
-
params[range_value] = norm_range_value params[range_value] if params[range_value].present?
|
119
|
-
end
|
120
|
-
|
121
|
-
transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort.join(",")
|
122
|
-
raw_transformation = options.delete(:raw_transformation)
|
123
|
-
transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
|
124
|
-
transformations = base_transformations << transformation
|
125
|
-
if responsive_width
|
126
|
-
responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
|
127
|
-
transformations << generate_transformation_string(responsive_width_transformation.clone)
|
128
|
-
end
|
129
|
-
|
130
|
-
if width.to_s == "auto" || responsive_width
|
131
|
-
options[:responsive] = true
|
132
|
-
end
|
133
|
-
if dpr.to_s == "auto"
|
134
|
-
options[:hidpi] = true
|
135
|
-
end
|
136
|
-
|
137
|
-
transformations.reject(&:blank?).join("/")
|
138
|
-
end
|
139
|
-
|
140
|
-
def self.api_string_to_sign(params_to_sign)
|
141
|
-
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("&")
|
142
|
-
end
|
143
|
-
|
144
|
-
def self.api_sign_request(params_to_sign, api_secret)
|
145
|
-
to_sign = api_string_to_sign(params_to_sign)
|
146
|
-
Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
|
147
|
-
end
|
148
|
-
|
149
|
-
# Warning: options are being destructively updated!
|
150
|
-
def self.unsigned_download_url(source, options = {})
|
151
|
-
|
152
|
-
type = options.delete(:type)
|
153
|
-
|
154
|
-
options[:fetch_format] ||= options.delete(:format) if type == :fetch
|
155
|
-
transformation = self.generate_transformation_string(options)
|
156
|
-
|
157
|
-
resource_type = options.delete(:resource_type)
|
158
|
-
version = options.delete(:version)
|
159
|
-
format = options.delete(:format)
|
160
|
-
cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
|
161
|
-
|
162
|
-
secure = options.delete(:secure)
|
163
|
-
ssl_detected = options.delete(:ssl_detected)
|
164
|
-
secure = ssl_detected || Cloudinary.config.secure if secure.nil?
|
165
|
-
private_cdn = config_option_consume(options, :private_cdn)
|
166
|
-
secure_distribution = config_option_consume(options, :secure_distribution)
|
167
|
-
cname = config_option_consume(options, :cname)
|
168
|
-
shorten = config_option_consume(options, :shorten)
|
169
|
-
force_remote = options.delete(:force_remote)
|
170
|
-
cdn_subdomain = config_option_consume(options, :cdn_subdomain)
|
171
|
-
secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
|
172
|
-
sign_url = config_option_consume(options, :sign_url)
|
173
|
-
secret = config_option_consume(options, :api_secret)
|
174
|
-
sign_version = config_option_consume(options, :sign_version) # Deprecated behavior
|
175
|
-
url_suffix = options.delete(:url_suffix)
|
176
|
-
use_root_path = config_option_consume(options, :use_root_path)
|
177
|
-
|
178
|
-
raise(CloudinaryException, "URL Suffix only supported in private CDN") if url_suffix.present? and not private_cdn
|
179
|
-
|
180
|
-
original_source = source
|
181
|
-
return original_source if source.blank?
|
182
|
-
if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
source = smart_escape(
|
228
|
-
source_to_sign = source
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
source = "#{source}
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
resource_type = "
|
249
|
-
type = nil
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
#
|
273
|
-
#
|
274
|
-
# if
|
275
|
-
#
|
276
|
-
# if cdn_domain is true uses
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
prefix = "
|
297
|
-
|
298
|
-
|
299
|
-
prefix = "http://#{
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
params
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
:
|
328
|
-
:
|
329
|
-
:
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
signer
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
when
|
449
|
-
when
|
450
|
-
when
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
#
|
511
|
-
#
|
512
|
-
#
|
513
|
-
#
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
end
|
1
|
+
# Copyright Cloudinary
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'zlib'
|
4
|
+
require 'uri'
|
5
|
+
require 'aws_cf_signer'
|
6
|
+
|
7
|
+
class Cloudinary::Utils
|
8
|
+
# @deprecated Use Cloudinary::SHARED_CDN
|
9
|
+
SHARED_CDN = Cloudinary::SHARED_CDN
|
10
|
+
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
|
11
|
+
|
12
|
+
# Warning: options are being destructively updated!
|
13
|
+
def self.generate_transformation_string(options={})
|
14
|
+
if options.is_a?(Array)
|
15
|
+
return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
|
16
|
+
end
|
17
|
+
# Symbolize keys
|
18
|
+
options.keys.each do |key|
|
19
|
+
options[(key.to_sym rescue key)] = options.delete(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
responsive_width = config_option_consume(options, :responsive_width)
|
23
|
+
size = options.delete(:size)
|
24
|
+
options[:width], options[:height] = size.split("x") if size
|
25
|
+
width = options[:width]
|
26
|
+
width = width.to_s if width.is_a?(Symbol)
|
27
|
+
height = options[:height]
|
28
|
+
has_layer = options[:overlay].present? || options[:underlay].present?
|
29
|
+
|
30
|
+
crop = options.delete(:crop)
|
31
|
+
angle = build_array(options.delete(:angle)).join(".")
|
32
|
+
|
33
|
+
no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
|
34
|
+
options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width == "auto" || responsive_width)
|
35
|
+
options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)
|
36
|
+
|
37
|
+
width=height=nil if crop.nil? && !has_layer && width != "auto"
|
38
|
+
|
39
|
+
background = options.delete(:background)
|
40
|
+
background = background.sub(/^#/, 'rgb:') if background
|
41
|
+
|
42
|
+
color = options.delete(:color)
|
43
|
+
color = color.sub(/^#/, 'rgb:') if color
|
44
|
+
|
45
|
+
base_transformations = build_array(options.delete(:transformation))
|
46
|
+
if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
|
47
|
+
base_transformations = base_transformations.map do
|
48
|
+
|base_transformation|
|
49
|
+
base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone) : generate_transformation_string(:transformation=>base_transformation)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
named_transformation = base_transformations.join(".")
|
53
|
+
base_transformations = []
|
54
|
+
end
|
55
|
+
|
56
|
+
effect = options.delete(:effect)
|
57
|
+
effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)
|
58
|
+
|
59
|
+
border = options.delete(:border)
|
60
|
+
if border.is_a?(Hash)
|
61
|
+
border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
|
62
|
+
elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
|
63
|
+
options[:border] = border
|
64
|
+
border = nil
|
65
|
+
end
|
66
|
+
flags = build_array(options.delete(:flags)).join(".")
|
67
|
+
dpr = config_option_consume(options, :dpr)
|
68
|
+
|
69
|
+
if options.include? :offset
|
70
|
+
options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
|
71
|
+
end
|
72
|
+
|
73
|
+
params = {
|
74
|
+
:a => angle,
|
75
|
+
:b => background,
|
76
|
+
:bo => border,
|
77
|
+
:c => crop,
|
78
|
+
:co => color,
|
79
|
+
:dpr => dpr,
|
80
|
+
:e => effect,
|
81
|
+
:fl => flags,
|
82
|
+
:h => height,
|
83
|
+
:t => named_transformation,
|
84
|
+
:w => width
|
85
|
+
}
|
86
|
+
{
|
87
|
+
:ac => :audio_codec,
|
88
|
+
:br => :bit_rate,
|
89
|
+
:cs => :color_space,
|
90
|
+
:d => :default_image,
|
91
|
+
:dl => :delay,
|
92
|
+
:dn => :density,
|
93
|
+
:du => :duration,
|
94
|
+
:eo => :end_offset,
|
95
|
+
:f => :fetch_format,
|
96
|
+
:g => :gravity,
|
97
|
+
:l => :overlay,
|
98
|
+
:o => :opacity,
|
99
|
+
:p => :prefix,
|
100
|
+
:pg => :page,
|
101
|
+
:q => :quality,
|
102
|
+
:r => :radius,
|
103
|
+
:af => :audio_frequency,
|
104
|
+
:so => :start_offset,
|
105
|
+
:u => :underlay,
|
106
|
+
:vc => :video_codec,
|
107
|
+
:vs => :video_sampling,
|
108
|
+
:x => :x,
|
109
|
+
:y => :y,
|
110
|
+
:z => :zoom
|
111
|
+
}.each do
|
112
|
+
|param, option|
|
113
|
+
params[param] = options.delete(option)
|
114
|
+
end
|
115
|
+
|
116
|
+
params[:vc] = process_video_params params[:vc] if params[:vc].present?
|
117
|
+
[:so, :eo, :du].each do |range_value|
|
118
|
+
params[range_value] = norm_range_value params[range_value] if params[range_value].present?
|
119
|
+
end
|
120
|
+
|
121
|
+
transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort.join(",")
|
122
|
+
raw_transformation = options.delete(:raw_transformation)
|
123
|
+
transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
|
124
|
+
transformations = base_transformations << transformation
|
125
|
+
if responsive_width
|
126
|
+
responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
|
127
|
+
transformations << generate_transformation_string(responsive_width_transformation.clone)
|
128
|
+
end
|
129
|
+
|
130
|
+
if width.to_s == "auto" || responsive_width
|
131
|
+
options[:responsive] = true
|
132
|
+
end
|
133
|
+
if dpr.to_s == "auto"
|
134
|
+
options[:hidpi] = true
|
135
|
+
end
|
136
|
+
|
137
|
+
transformations.reject(&:blank?).join("/")
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.api_string_to_sign(params_to_sign)
|
141
|
+
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("&")
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.api_sign_request(params_to_sign, api_secret)
|
145
|
+
to_sign = api_string_to_sign(params_to_sign)
|
146
|
+
Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
|
147
|
+
end
|
148
|
+
|
149
|
+
# Warning: options are being destructively updated!
|
150
|
+
def self.unsigned_download_url(source, options = {})
|
151
|
+
|
152
|
+
type = options.delete(:type)
|
153
|
+
|
154
|
+
options[:fetch_format] ||= options.delete(:format) if type == :fetch
|
155
|
+
transformation = self.generate_transformation_string(options)
|
156
|
+
|
157
|
+
resource_type = options.delete(:resource_type)
|
158
|
+
version = options.delete(:version)
|
159
|
+
format = options.delete(:format)
|
160
|
+
cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
|
161
|
+
|
162
|
+
secure = options.delete(:secure)
|
163
|
+
ssl_detected = options.delete(:ssl_detected)
|
164
|
+
secure = ssl_detected || Cloudinary.config.secure if secure.nil?
|
165
|
+
private_cdn = config_option_consume(options, :private_cdn)
|
166
|
+
secure_distribution = config_option_consume(options, :secure_distribution)
|
167
|
+
cname = config_option_consume(options, :cname)
|
168
|
+
shorten = config_option_consume(options, :shorten)
|
169
|
+
force_remote = options.delete(:force_remote)
|
170
|
+
cdn_subdomain = config_option_consume(options, :cdn_subdomain)
|
171
|
+
secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
|
172
|
+
sign_url = config_option_consume(options, :sign_url)
|
173
|
+
secret = config_option_consume(options, :api_secret)
|
174
|
+
sign_version = config_option_consume(options, :sign_version) # Deprecated behavior
|
175
|
+
url_suffix = options.delete(:url_suffix)
|
176
|
+
use_root_path = config_option_consume(options, :use_root_path)
|
177
|
+
|
178
|
+
raise(CloudinaryException, "URL Suffix only supported in private CDN") if url_suffix.present? and not private_cdn
|
179
|
+
|
180
|
+
original_source = source
|
181
|
+
return original_source if source.blank?
|
182
|
+
if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
|
183
|
+
resource_type ||= source.resource_type
|
184
|
+
type ||= source.storage_type
|
185
|
+
source = format.blank? ? source.filename : source.full_public_id
|
186
|
+
end
|
187
|
+
resource_type ||= "image"
|
188
|
+
source = source.to_s
|
189
|
+
if !force_remote
|
190
|
+
return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
|
191
|
+
if source.start_with?("/")
|
192
|
+
if source.start_with?("/images/")
|
193
|
+
source = source.sub(%r(/images/), '')
|
194
|
+
else
|
195
|
+
return original_source
|
196
|
+
end
|
197
|
+
end
|
198
|
+
@metadata ||= defined?(Cloudinary::Static) ? Cloudinary::Static.metadata : {}
|
199
|
+
if type == :asset && @metadata["images/#{source}"]
|
200
|
+
return original_source if !Cloudinary.config.static_image_support
|
201
|
+
source = @metadata["images/#{source}"]["public_id"]
|
202
|
+
source += File.extname(original_source) if !format
|
203
|
+
elsif type == :asset
|
204
|
+
return original_source # requested asset, but no metadata - probably local file. return.
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
209
|
+
source, source_to_sign = finalize_source(source, format, url_suffix)
|
210
|
+
|
211
|
+
version ||= 1 if source_to_sign.include?("/") and !source_to_sign.match(/^v[0-9]+/) and !source_to_sign.match(/^https?:\//)
|
212
|
+
version &&= "v#{version}"
|
213
|
+
|
214
|
+
transformation = transformation.gsub(%r(([^:])//), '\1/')
|
215
|
+
if sign_url
|
216
|
+
to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
|
217
|
+
signature = 's--' + Base64.urlsafe_encode64(Digest::SHA1.digest(to_sign + secret))[0,8] + '--'
|
218
|
+
end
|
219
|
+
|
220
|
+
prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
|
221
|
+
source = [prefix, resource_type, type, signature, transformation, version, source].reject(&:blank?).join("/")
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.finalize_source(source, format, url_suffix)
|
225
|
+
source = source.gsub(%r(([^:])//), '\1/')
|
226
|
+
if source.match(%r(^https?:/)i)
|
227
|
+
source = smart_escape(source)
|
228
|
+
source_to_sign = source
|
229
|
+
else
|
230
|
+
source = smart_escape(URI.decode(source))
|
231
|
+
source_to_sign = source
|
232
|
+
unless url_suffix.blank?
|
233
|
+
raise(CloudinaryException, "url_suffix should not include . or /") if url_suffix.match(%r([\./]))
|
234
|
+
source = "#{source}/#{url_suffix}"
|
235
|
+
end
|
236
|
+
if !format.blank?
|
237
|
+
source = "#{source}.#{format}"
|
238
|
+
source_to_sign = "#{source_to_sign}.#{format}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
[source, source_to_sign]
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
245
|
+
type ||= :upload
|
246
|
+
if !url_suffix.blank?
|
247
|
+
if resource_type.to_s == "image" && type.to_s == "upload"
|
248
|
+
resource_type = "images"
|
249
|
+
type = nil
|
250
|
+
elsif resource_type.to_s == "raw" && type.to_s == "upload"
|
251
|
+
resource_type = "files"
|
252
|
+
type = nil
|
253
|
+
else
|
254
|
+
raise(CloudinaryException, "URL Suffix only supported for image/upload and raw/upload")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
if use_root_path
|
258
|
+
if (resource_type.to_s == "image" && type.to_s == "upload") || (resource_type.to_s == "images" && type.blank?)
|
259
|
+
resource_type = nil
|
260
|
+
type = nil
|
261
|
+
else
|
262
|
+
raise(CloudinaryException, "Root path only supported for image/upload")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
if shorten && resource_type.to_s == "image" && type.to_s == "upload"
|
266
|
+
resource_type = "iu"
|
267
|
+
type = nil
|
268
|
+
end
|
269
|
+
[resource_type, type]
|
270
|
+
end
|
271
|
+
|
272
|
+
# cdn_subdomain and secure_cdn_subdomain
|
273
|
+
# 1) Customers in shared distribution (e.g. res.cloudinary.com)
|
274
|
+
# 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.
|
275
|
+
# 2) Customers with private cdn
|
276
|
+
# if cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for http
|
277
|
+
# if secure_cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for https (please contact support if you require this)
|
278
|
+
# 3) Customers with cname
|
279
|
+
# if cdn_domain is true uses a[1-5].cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.
|
280
|
+
def self.unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
|
281
|
+
return "/res#{cloud_name}" if cloud_name.start_with?("/") # For development
|
282
|
+
|
283
|
+
shared_domain = !private_cdn
|
284
|
+
|
285
|
+
if secure
|
286
|
+
if secure_distribution.nil? || secure_distribution == Cloudinary::OLD_AKAMAI_SHARED_CDN
|
287
|
+
secure_distribution = private_cdn ? "#{cloud_name}-res.cloudinary.com" : Cloudinary::SHARED_CDN
|
288
|
+
end
|
289
|
+
shared_domain ||= secure_distribution == Cloudinary::SHARED_CDN
|
290
|
+
secure_cdn_subdomain = cdn_subdomain if secure_cdn_subdomain.nil? && shared_domain
|
291
|
+
|
292
|
+
if secure_cdn_subdomain
|
293
|
+
secure_distribution = secure_distribution.gsub('res.cloudinary.com', "res-#{(Zlib::crc32(source) % 5) + 1}.cloudinary.com")
|
294
|
+
end
|
295
|
+
|
296
|
+
prefix = "https://#{secure_distribution}"
|
297
|
+
elsif cname
|
298
|
+
subdomain = cdn_subdomain ? "a#{(Zlib::crc32(source) % 5) + 1}." : ""
|
299
|
+
prefix = "http://#{subdomain}#{cname}"
|
300
|
+
else
|
301
|
+
host = [private_cdn ? "#{cloud_name}-" : "", "res", cdn_subdomain ? "-#{(Zlib::crc32(source) % 5) + 1}" : "", ".cloudinary.com"].join
|
302
|
+
prefix = "http://#{host}"
|
303
|
+
end
|
304
|
+
prefix += "/#{cloud_name}" if shared_domain
|
305
|
+
|
306
|
+
prefix
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.cloudinary_api_url(action = 'upload', options = {})
|
310
|
+
cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
|
311
|
+
cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, "Must supply cloud_name")
|
312
|
+
resource_type = options[:resource_type] || "image"
|
313
|
+
return [cloudinary, "v1_1", cloud_name, resource_type, action].join("/")
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.sign_request(params, options={})
|
317
|
+
api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
|
318
|
+
api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
|
319
|
+
params = params.reject{|k, v| self.safe_blank?(v)}
|
320
|
+
params[:signature] = Cloudinary::Utils.api_sign_request(params, api_secret)
|
321
|
+
params[:api_key] = api_key
|
322
|
+
params
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.private_download_url(public_id, format, options = {})
|
326
|
+
cloudinary_params = sign_request({
|
327
|
+
:timestamp=>Time.now.to_i,
|
328
|
+
:public_id=>public_id,
|
329
|
+
:format=>format,
|
330
|
+
:type=>options[:type],
|
331
|
+
:attachment=>options[:attachment],
|
332
|
+
:expires_at=>options[:expires_at] && options[:expires_at].to_i
|
333
|
+
}, options)
|
334
|
+
|
335
|
+
return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + cloudinary_params.to_query
|
336
|
+
end
|
337
|
+
|
338
|
+
def self.zip_download_url(tag, options = {})
|
339
|
+
cloudinary_params = sign_request({:timestamp=>Time.now.to_i, :tag=>tag, :transformation=>generate_transformation_string(options)}, options)
|
340
|
+
return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + cloudinary_params.to_query
|
341
|
+
end
|
342
|
+
|
343
|
+
def self.signed_download_url(public_id, options = {})
|
344
|
+
aws_private_key_path = options[:aws_private_key_path] || Cloudinary.config.aws_private_key_path || raise(CloudinaryException, "Must supply aws_private_key_path")
|
345
|
+
aws_key_pair_id = options[:aws_key_pair_id] || Cloudinary.config.aws_key_pair_id || raise(CloudinaryException, "Must supply aws_key_pair_id")
|
346
|
+
authenticated_distribution = options[:authenticated_distribution] || Cloudinary.config.authenticated_distribution || raise(CloudinaryException, "Must supply authenticated_distribution")
|
347
|
+
@signers ||= Hash.new{|h,k| path, id = k; h[k] = AwsCfSigner.new(path, id)}
|
348
|
+
signer = @signers[[aws_private_key_path, aws_key_pair_id]]
|
349
|
+
url = Cloudinary::Utils.unsigned_download_url(public_id, {:type=>:authenticated}.merge(options).merge(:secure=>true, :secure_distribution=>authenticated_distribution, :private_cdn=>true))
|
350
|
+
expires_at = options[:expires_at] || (Time.now+3600)
|
351
|
+
signer.sign(url, :ending => expires_at)
|
352
|
+
end
|
353
|
+
|
354
|
+
def self.cloudinary_url(public_id, options = {})
|
355
|
+
if options[:type].to_s == 'authenticated' && !options[:sign_url]
|
356
|
+
result = signed_download_url(public_id, options)
|
357
|
+
else
|
358
|
+
result = unsigned_download_url(public_id, options)
|
359
|
+
end
|
360
|
+
return result
|
361
|
+
end
|
362
|
+
|
363
|
+
def self.asset_file_name(path)
|
364
|
+
data = Cloudinary.app_root.join(path).read(:mode=>"rb")
|
365
|
+
ext = path.extname
|
366
|
+
md5 = Digest::MD5.hexdigest(data)
|
367
|
+
public_id = "#{path.basename(ext)}-#{md5}"
|
368
|
+
"#{public_id}#{ext}"
|
369
|
+
end
|
370
|
+
|
371
|
+
# Based on CGI::unescape. In addition does not escape / :
|
372
|
+
def self.smart_escape(string)
|
373
|
+
string.gsub(/([^a-zA-Z0-9_.\-\/:]+)/) do
|
374
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def self.random_public_id
|
379
|
+
sr = defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom
|
380
|
+
sr.base64(20).downcase.gsub(/[^a-z0-9]/, "").sub(/^[0-9]+/, '')[0,20]
|
381
|
+
end
|
382
|
+
|
383
|
+
def self.signed_preloaded_image(result)
|
384
|
+
"#{result["resource_type"]}/#{result["type"] || "upload"}/v#{result["version"]}/#{[result["public_id"], result["format"]].reject(&:blank?).join(".")}##{result["signature"]}"
|
385
|
+
end
|
386
|
+
|
387
|
+
@@json_decode = false
|
388
|
+
def self.json_decode(str)
|
389
|
+
if !@@json_decode
|
390
|
+
@@json_decode = true
|
391
|
+
begin
|
392
|
+
require 'json'
|
393
|
+
rescue LoadError
|
394
|
+
begin
|
395
|
+
require 'active_support/json'
|
396
|
+
rescue LoadError
|
397
|
+
raise LoadError, "Please add the json gem or active_support to your Gemfile"
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
defined?(JSON) ? JSON.parse(str) : ActiveSupport::JSON.decode(str)
|
402
|
+
end
|
403
|
+
|
404
|
+
def self.build_array(array)
|
405
|
+
case array
|
406
|
+
when Array then array
|
407
|
+
when nil then []
|
408
|
+
else [array]
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def self.encode_hash(hash)
|
413
|
+
case hash
|
414
|
+
when Hash then hash.map{|k,v| "#{k}=#{v}"}.join("|")
|
415
|
+
when nil then ""
|
416
|
+
else hash
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def self.encode_double_array(array)
|
421
|
+
array = build_array(array)
|
422
|
+
if array.length > 0 && array[0].is_a?(Array)
|
423
|
+
return array.map{|a| build_array(a).join(",")}.join("|")
|
424
|
+
else
|
425
|
+
return array.join(",")
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
IMAGE_FORMATS = %w(bmp png tif tiff jpg jpeg gif pdf ico eps jpc jp2 psd)
|
430
|
+
|
431
|
+
def self.supported_image_format?(format)
|
432
|
+
format = format.to_s.downcase
|
433
|
+
extension = format =~ /\./ ? format.split('.').last : format
|
434
|
+
IMAGE_FORMATS.include?(extension)
|
435
|
+
end
|
436
|
+
|
437
|
+
def self.resource_type_for_format(format)
|
438
|
+
self.supported_image_format?(format) ? 'image' : 'raw'
|
439
|
+
end
|
440
|
+
|
441
|
+
def self.config_option_consume(options, option_name, default_value = nil)
|
442
|
+
return options.delete(option_name) if options.include?(option_name)
|
443
|
+
return Cloudinary.config.send(option_name) || default_value
|
444
|
+
end
|
445
|
+
|
446
|
+
def self.as_bool(value)
|
447
|
+
case value
|
448
|
+
when nil then nil
|
449
|
+
when String then value.downcase == "true" || value == "1"
|
450
|
+
when TrueClass then true
|
451
|
+
when FalseClass then false
|
452
|
+
when Fixnum then value != 0
|
453
|
+
when Symbol then value == :true
|
454
|
+
else
|
455
|
+
raise "Invalid boolean value #{value} of type #{value.class}"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def self.as_safe_bool(value)
|
460
|
+
case as_bool(value)
|
461
|
+
when nil then nil
|
462
|
+
when TrueClass then 1
|
463
|
+
when FalseClass then 0
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def self.safe_blank?(value)
|
468
|
+
value.nil? || value == "" || value == []
|
469
|
+
end
|
470
|
+
|
471
|
+
private
|
472
|
+
def self.number_pattern
|
473
|
+
"([0-9]*)\\.([0-9]+)|([0-9]+)"
|
474
|
+
end
|
475
|
+
|
476
|
+
def self.offset_any_pattern
|
477
|
+
"(#{number_pattern})([%pP])?"
|
478
|
+
end
|
479
|
+
|
480
|
+
def self.offset_any_pattern_re
|
481
|
+
/((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)\.\.((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)/
|
482
|
+
end
|
483
|
+
|
484
|
+
# Split a range into the start and end values
|
485
|
+
def self.split_range(range) # :nodoc:
|
486
|
+
case range
|
487
|
+
when Range
|
488
|
+
[range.first, range.last]
|
489
|
+
when String
|
490
|
+
range.split ".." if offset_any_pattern_re =~ range
|
491
|
+
when Array
|
492
|
+
[range.first, range.last]
|
493
|
+
else
|
494
|
+
nil
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# Normalize an offset value
|
499
|
+
# @param [String] value a decimal value which may have a 'p' or '%' postfix. E.g. '35%', '0.4p'
|
500
|
+
# @return [Object|String] a normalized String of the input value if possible otherwise the value itself
|
501
|
+
def self.norm_range_value(value) # :nodoc:
|
502
|
+
offset = /^#{offset_any_pattern}$/.match( value.to_s)
|
503
|
+
if offset
|
504
|
+
modifier = offset[5].present? ? 'p' : ''
|
505
|
+
value = "#{offset[1]}#{modifier}"
|
506
|
+
end
|
507
|
+
value
|
508
|
+
end
|
509
|
+
|
510
|
+
# A video codec parameter can be either a String or a Hash.
|
511
|
+
#
|
512
|
+
# @param [Object] param <code>vc_<codec>[ : <profile> : [<level>]]</code>
|
513
|
+
# or <code>{ codec: 'h264', profile: 'basic', level: '3.1' }</code>
|
514
|
+
# @return [String] <code><codec> : <profile> : [<level>]]</code> if a Hash was provided
|
515
|
+
# or the param if a String was provided.
|
516
|
+
# Returns NIL if param is not a Hash or String
|
517
|
+
def self.process_video_params(param)
|
518
|
+
case param
|
519
|
+
when Hash
|
520
|
+
video = ""
|
521
|
+
if param.has_key? :codec
|
522
|
+
video = param[:codec]
|
523
|
+
if param.has_key? :profile
|
524
|
+
video.concat ":" + param[:profile]
|
525
|
+
if param.has_key? :level
|
526
|
+
video.concat ":" + param[:level]
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
video
|
531
|
+
when String
|
532
|
+
param
|
533
|
+
else
|
534
|
+
nil
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def self.deep_symbolize_keys(object)
|
539
|
+
case object
|
540
|
+
when Hash
|
541
|
+
result = {}
|
542
|
+
object.each do |key, value|
|
543
|
+
key = key.to_sym rescue key
|
544
|
+
result[key] = deep_symbolize_keys(value)
|
545
|
+
end
|
546
|
+
result
|
547
|
+
when Array
|
548
|
+
object.map{|e| deep_symbolize_keys(e)}
|
549
|
+
else
|
550
|
+
object
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
end
|