cloudinary 1.0.85 → 1.1.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.
- 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 | 
            -
              **Take a look at our [Getting started guide of Ruby on Rails](http://cloudinary.com/documentation/rails_integration#getting_started_guide)**.
         | 
| 15 | 
            +
              **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 | 
            -
             **See [our documentation](http://cloudinary.com/documentation/rails_image_manipulation) for more information about displaying and transforming images in Rails**.                                         
         | 
| 120 | 
            +
             **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 | 
            -
             **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 | 
            +
             **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 | 
            -
             **For more details on CarrierWave integration see [our documentation](http://cloudinary.com/documentation/rails_carrierwave)**.
         | 
| 162 | 
            +
             **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
         |