cloudinary 1.9.1 → 1.20.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.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  4. data/.github/pull_request_template.md +24 -0
  5. data/.gitignore +7 -1
  6. data/.travis.yml +15 -8
  7. data/CHANGELOG.md +261 -0
  8. data/README.md +3 -0
  9. data/Rakefile +3 -45
  10. data/cloudinary.gemspec +27 -20
  11. data/lib/active_storage/blob_key.rb +20 -0
  12. data/lib/active_storage/service/cloudinary_service.rb +249 -0
  13. data/lib/cloudinary.rb +53 -63
  14. data/lib/cloudinary/account_api.rb +231 -0
  15. data/lib/cloudinary/account_config.rb +30 -0
  16. data/lib/cloudinary/api.rb +228 -71
  17. data/lib/cloudinary/auth_token.rb +10 -4
  18. data/lib/cloudinary/base_api.rb +79 -0
  19. data/lib/cloudinary/base_config.rb +70 -0
  20. data/lib/cloudinary/cache.rb +38 -0
  21. data/lib/cloudinary/cache/breakpoints_cache.rb +31 -0
  22. data/lib/cloudinary/cache/key_value_cache_adapter.rb +25 -0
  23. data/lib/cloudinary/cache/rails_cache_adapter.rb +34 -0
  24. data/lib/cloudinary/cache/storage/rails_cache_storage.rb +5 -0
  25. data/lib/cloudinary/carrier_wave.rb +4 -2
  26. data/lib/cloudinary/carrier_wave/remote.rb +3 -2
  27. data/lib/cloudinary/carrier_wave/storage.rb +2 -1
  28. data/lib/cloudinary/{controller.rb → cloudinary_controller.rb} +3 -5
  29. data/lib/cloudinary/config.rb +43 -0
  30. data/lib/cloudinary/helper.rb +77 -7
  31. data/lib/cloudinary/migrator.rb +3 -1
  32. data/lib/cloudinary/railtie.rb +7 -3
  33. data/lib/cloudinary/responsive.rb +111 -0
  34. data/lib/cloudinary/uploader.rb +67 -15
  35. data/lib/cloudinary/utils.rb +324 -54
  36. data/lib/cloudinary/version.rb +1 -1
  37. data/lib/cloudinary/video_helper.rb +96 -22
  38. data/lib/tasks/cloudinary/fetch_assets.rake +48 -0
  39. data/lib/tasks/{cloudinary.rake → cloudinary/sync_static.rake} +0 -0
  40. data/tools/allocate_test_cloud.sh +9 -0
  41. data/tools/get_test_cloud.sh +9 -0
  42. data/tools/update_version +220 -0
  43. data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +51 -13
  44. data/vendor/assets/javascripts/cloudinary/jquery.fileupload.js +24 -4
  45. data/vendor/assets/javascripts/cloudinary/jquery.ui.widget.js +741 -561
  46. data/vendor/assets/javascripts/cloudinary/load-image.all.min.js +1 -1
  47. metadata +92 -67
  48. data/spec/access_control_spec.rb +0 -99
  49. data/spec/api_spec.rb +0 -545
  50. data/spec/archive_spec.rb +0 -129
  51. data/spec/auth_token_spec.rb +0 -79
  52. data/spec/cloudinary_helper_spec.rb +0 -190
  53. data/spec/cloudinary_spec.rb +0 -32
  54. data/spec/data/sync_static/app/assets/javascripts/1.coffee +0 -1
  55. data/spec/data/sync_static/app/assets/javascripts/1.js +0 -1
  56. data/spec/data/sync_static/app/assets/stylesheets/1.css +0 -3
  57. data/spec/docx.docx +0 -0
  58. data/spec/favicon.ico +0 -0
  59. data/spec/logo.png +0 -0
  60. data/spec/rake_spec.rb +0 -160
  61. data/spec/sample_asset_file.tsv +0 -4
  62. data/spec/search_spec.rb +0 -109
  63. data/spec/spec_helper.rb +0 -245
  64. data/spec/storage_spec.rb +0 -44
  65. data/spec/streaminig_profiles_api_spec.rb +0 -74
  66. data/spec/support/helpers/temp_file_helpers.rb +0 -22
  67. data/spec/support/shared_contexts/rake.rb +0 -19
  68. data/spec/uploader_spec.rb +0 -363
  69. data/spec/utils_methods_spec.rb +0 -54
  70. data/spec/utils_spec.rb +0 -906
  71. data/spec/video_tag_spec.rb +0 -251
  72. data/spec/video_url_spec.rb +0 -164
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  if RUBY_VERSION > "2"
3
5
  require "ostruct"
@@ -10,6 +12,7 @@ module Cloudinary
10
12
  module AuthToken
11
13
  SEPARATOR = '~'
12
14
  UNSAFE = /[ "#%&\'\/:;<=>?@\[\\\]^`{\|}~]/
15
+ EMPTY_TOKEN = {}.freeze
13
16
 
14
17
  def self.generate(options = {})
15
18
  key = options[:key]
@@ -30,6 +33,10 @@ module Cloudinary
30
33
  end
31
34
  end
32
35
 
36
+ if url.blank? && acl.blank?
37
+ raise 'AuthToken must contain either an acl or a url property'
38
+ end
39
+
33
40
  token = []
34
41
  token << "ip=#{ip}" if ip
35
42
  token << "st=#{start}" if start
@@ -42,12 +49,11 @@ module Cloudinary
42
49
  "#{name}=#{token.join(SEPARATOR)}"
43
50
  end
44
51
 
45
-
46
52
  # Merge token2 to token1 returning a new
47
53
  # Requires to support Ruby 1.9
48
54
  def self.merge_auth_token(token1, token2)
49
- token1 = token1 || {}
50
- token2 = token2 || {}
55
+ token1 = token1 || EMPTY_TOKEN
56
+ token2 = token2 || EMPTY_TOKEN
51
57
  token1 = token1.respond_to?( :to_h) ? token1.to_h : token1
52
58
  token2 = token2.respond_to?( :to_h) ? token2.to_h : token2
53
59
  token1.merge(token2)
@@ -69,4 +75,4 @@ module Cloudinary
69
75
  OpenSSL::HMAC.hexdigest(digest, bin_key, message)
70
76
  end
71
77
  end
72
- end
78
+ end
@@ -0,0 +1,79 @@
1
+ require "rest_client"
2
+ require "json"
3
+
4
+ module Cloudinary::BaseApi
5
+ class Error < CloudinaryException; end
6
+ class NotFound < Error; end
7
+ class NotAllowed < Error; end
8
+ class AlreadyExists < Error; end
9
+ class RateLimited < Error; end
10
+ class BadRequest < Error; end
11
+ class GeneralError < Error; end
12
+ class AuthorizationRequired < Error; end
13
+
14
+ class Response < Hash
15
+ attr_reader :rate_limit_reset_at, :rate_limit_remaining, :rate_limit_allowed
16
+
17
+ def initialize(response=nil)
18
+ if response
19
+ # This sets the instantiated self as the response Hash
20
+ update Cloudinary::Api.parse_json_response response
21
+
22
+ @rate_limit_allowed = response.headers[:x_featureratelimit_limit].to_i if response.headers[:x_featureratelimit_limit]
23
+ @rate_limit_reset_at = Time.parse(response.headers[:x_featureratelimit_reset]) if response.headers[:x_featureratelimit_reset]
24
+ @rate_limit_remaining = response.headers[:x_featureratelimit_remaining].to_i if response.headers[:x_featureratelimit_remaining]
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.extended(base)
30
+ [Error, NotFound, NotAllowed, AlreadyExists, RateLimited, BadRequest, GeneralError, AuthorizationRequired, Response].each do |constant|
31
+ base.const_set(constant.name.split("::").last, constant)
32
+ end
33
+ end
34
+
35
+ def call_json_api(method, api_url, payload, timeout, headers, proxy = nil, user = nil, password = nil)
36
+ RestClient::Request.execute(method: method,
37
+ url: api_url,
38
+ payload: payload,
39
+ timeout: timeout,
40
+ headers: headers,
41
+ proxy: proxy,
42
+ user: user,
43
+ password: password) do |response|
44
+ return Response.new(response) if response.code == 200
45
+ exception_class = case response.code
46
+ when 400 then BadRequest
47
+ when 401 then AuthorizationRequired
48
+ when 403 then NotAllowed
49
+ when 404 then NotFound
50
+ when 409 then AlreadyExists
51
+ when 420 then RateLimited
52
+ when 500 then GeneralError
53
+ else raise GeneralError.new("Server returned unexpected status code - #{response.code} - #{response.body}")
54
+ end
55
+ json = Cloudinary::Api.parse_json_response(response)
56
+ raise exception_class.new(json["error"]["message"])
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def call_cloudinary_api(method, uri, user, password, params, options, &api_url_builder)
63
+ cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || 'https://api.cloudinary.com'
64
+ api_url = Cloudinary::Utils.smart_escape(api_url_builder.call(cloudinary, uri).flatten.join('/'))
65
+ timeout = options[:timeout] || Cloudinary.config.timeout || 60
66
+ proxy = options[:api_proxy] || Cloudinary.config.api_proxy
67
+
68
+ headers = { "User-Agent" => Cloudinary::USER_AGENT }
69
+
70
+ if options[:content_type] == :json
71
+ payload = params.to_json
72
+ headers.merge!("Content-Type" => "application/json", "Accept" => "application/json")
73
+ else
74
+ payload = params.reject { |_, v| v.nil? || v == "" }
75
+ end
76
+
77
+ call_json_api(method, api_url, payload, timeout, headers, proxy, user, password)
78
+ end
79
+ end
@@ -0,0 +1,70 @@
1
+ module Cloudinary
2
+ module BaseConfig
3
+ def load_from_url(url)
4
+ return unless url && !url.empty?
5
+
6
+ parsed_url = URI.parse(url)
7
+ scheme = parsed_url.scheme.to_s.downcase
8
+
9
+ if expected_scheme != scheme
10
+ raise(CloudinaryException,
11
+ "Invalid #{env_url} scheme. Expecting to start with '#{expected_scheme}://'")
12
+ end
13
+
14
+ update(config_from_parsed_url(parsed_url))
15
+ setup_from_parsed_url(parsed_url)
16
+ end
17
+
18
+ def update(new_config = {})
19
+ new_config.each{ |k,v| public_send(:"#{k}=", v) unless v.nil?}
20
+ end
21
+
22
+ def load_config_from_env
23
+ raise NotImplementedError
24
+ end
25
+
26
+ private
27
+
28
+ def config_from_parsed_url(parsed_url)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def env_url
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def expected_scheme
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def put_nested_key(key, value)
41
+ chain = key.split(/[\[\]]+/).reject(&:empty?)
42
+ outer = self
43
+ lastKey = chain.pop
44
+ chain.each do |innerKey|
45
+ inner = outer[innerKey]
46
+ if inner.nil?
47
+ inner = OpenStruct.new
48
+ outer[innerKey] = inner
49
+ end
50
+ outer = inner
51
+ end
52
+ outer[lastKey] = value
53
+ end
54
+
55
+ def is_nested_key?(key)
56
+ /\w+\[\w+\]/ =~ key
57
+ end
58
+
59
+ def setup_from_parsed_url(parsed_url)
60
+ parsed_url.query.to_s.split("&").each do |param|
61
+ key, value = param.split("=")
62
+ if is_nested_key? key
63
+ put_nested_key key, value
64
+ else
65
+ update(key => Utils.smart_unescape(value))
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,38 @@
1
+ require 'digest'
2
+
3
+ module Cloudinary
4
+ module Cache
5
+
6
+ class << self
7
+ attr_accessor :storage
8
+
9
+ def get(public_id, options)
10
+ if block_given?
11
+ storage.read(generate_cache_key(public_id, options)) {yield}
12
+ else
13
+ storage.read(generate_cache_key(public_id, options))
14
+ end
15
+ end
16
+
17
+ def set(public_id, options, value)
18
+ storage.write(generate_cache_key(public_id, options), value)
19
+ end
20
+
21
+ alias_method :fetch, :get
22
+
23
+ def flush_all
24
+ storage.clear
25
+ end
26
+
27
+ private
28
+
29
+ def generate_cache_key(public_id, options)
30
+ type = options[:type] || "upload"
31
+ resource_type = options[:resource_type] || "image"
32
+ transformation = Cloudinary::Utils.generate_transformation_string options.clone
33
+ format = options[:format]
34
+ Digest::SHA1.hexdigest [public_id, type, resource_type, transformation, format].reject(&:blank?).join('/')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ module Cloudinary::Cache
2
+ class BreakpointsCache
3
+ attr_accessor :adapter
4
+
5
+ def set(public_id, options, value)
6
+ upload_type, resource_type, transformation, format = options_to_parameters(options)
7
+ @adapter.set(public_id, upload_type, resource_type, transformation, format, value)
8
+
9
+ end
10
+
11
+ def fetch(public_id, options)
12
+ upload_type, resource_type, transformation, format = options_to_parameters(options)
13
+ @adapter.set(public_id, upload_type, resource_type, transformation, format, &Proc.new)
14
+
15
+ end
16
+
17
+ def get(public_id, options)
18
+ upload_type, resource_type, transformation, format = options_to_parameters(options)
19
+ @adapter.get(public_id, upload_type, resource_type, transformation, format)
20
+ end
21
+
22
+ def options_to_parameters(options)
23
+ options = Cloudinary::Utils.symbolize_keys options
24
+ transformation = Cloudinary::Utils.generate_transformation_string(options)
25
+ upload_type = options[:type] || 'upload'
26
+ resource_type = options[:resource_type] || 'image'
27
+ format = options[:format] || ""
28
+ [upload_type, resource_type, transformation, format]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ require 'digest'
2
+ module Cloudinary::Cache
3
+ class KeyValueCacheAdapter < CacheAdapter
4
+ def get(public_id, type, resource_type, transformation, format)
5
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
6
+ @storage.get(key)
7
+ end
8
+
9
+ def set(public_id, type, resource_type, transformation, format, value)
10
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
11
+ @storage.set(key, value)
12
+ end
13
+
14
+ def flush_all()
15
+ @storage.flush_all()
16
+ end
17
+
18
+ private
19
+
20
+ def generate_cache_key(public_id, type, resource_type, transformation, format)
21
+ Digest::SHA1.hexdigest [public_id, type, resource_type, transformation, format].reject(&:blank?)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Cloudinary::Cache
2
+ class RailsCacheAdapter < CacheAdapter
3
+
4
+ def flush_all
5
+ end
6
+
7
+ def get(public_id, type, resource_type, transformation, format)
8
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
9
+ Rails.cache.read(key)
10
+ end
11
+
12
+ def init
13
+ unless defined? Rails
14
+ raise CloudinaryException.new "Rails is required in order to use RailsCacheAdapter"
15
+ end
16
+ end
17
+
18
+ def set(public_id, type, resource_type, transformation, format, value)
19
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
20
+ Rails.cache.write(key, value)
21
+ end
22
+
23
+ def fetch(public_id, type, resource_type, transformation, format)
24
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
25
+ Rails.cache.fetch(key, &Proc.new)
26
+ end
27
+ private
28
+
29
+ def generate_cache_key(public_id, type, resource_type, transformation, format)
30
+ Digest::SHA1.hexdigest [public_id, type, resource_type, transformation, format].reject(&:blank?)
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Cloudinary::Cache::Storage
2
+ class RailsCacheStorage
3
+
4
+ end
5
+ end
@@ -3,14 +3,16 @@ require 'cloudinary/carrier_wave/process'
3
3
  require 'cloudinary/carrier_wave/error'
4
4
  require 'cloudinary/carrier_wave/remote'
5
5
  require 'cloudinary/carrier_wave/preloaded'
6
- require 'cloudinary/carrier_wave/storage'
6
+ require 'cloudinary/carrier_wave/storage' if defined?(::CarrierWave) # HACK
7
7
 
8
8
  module Cloudinary::CarrierWave
9
9
 
10
10
  def self.included(base)
11
11
  base.storage Cloudinary::CarrierWave::Storage
12
+ base.cache_storage = :file if base.cache_storage.blank?
12
13
  base.extend ClassMethods
13
- base.class_attribute :storage_type, :metadata
14
+ base.class_attribute :metadata
15
+ base.class_attribute :storage_type, instance_reader: false
14
16
  override_in_versions(base, :blank?, :full_public_id, :my_public_id, :all_versions_processors, :stored_version)
15
17
  end
16
18
 
@@ -1,10 +1,11 @@
1
1
  module Cloudinary::CarrierWave
2
2
  def download!(uri, *args)
3
- return super if !self.cloudinary_should_handle_remote?
3
+ return super unless self.cloudinary_should_handle_remote?
4
4
  if respond_to?(:process_uri)
5
5
  uri = process_uri(uri)
6
6
  else # Backward compatibility with old CarrierWave
7
- uri = URI.parse(URI.escape(URI.unescape(uri)))
7
+ remote_url_unsafe_chars = /([^a-zA-Z0-9_.\-\/:?&=]+)/ # In addition allow query string characters: "?","&" and "="
8
+ uri = URI.parse(Cloudinary::Utils.smart_escape(Cloudinary::Utils.smart_unescape(uri), remote_url_unsafe_chars))
8
9
  end
9
10
  return if uri.to_s.blank?
10
11
  self.original_filename = @cache_id = @filename = File.basename(uri.path).gsub(/[^a-zA-Z0-9\.\-\+_]/, '')
@@ -1,7 +1,8 @@
1
1
  class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
2
2
 
3
3
  def store!(file)
4
- return if !uploader.enable_processing
4
+ return unless uploader.enable_processing
5
+
5
6
  if uploader.is_main_uploader?
6
7
  case file
7
8
  when Cloudinary::CarrierWave::PreloadedCloudinaryFile
@@ -1,13 +1,11 @@
1
- module CloudinaryController
1
+ module Cloudinary::CloudinaryController
2
2
  protected
3
-
3
+
4
4
  def valid_cloudinary_response?
5
5
  received_signature = request.query_parameters[:signature]
6
6
  calculated_signature = Cloudinary::Utils.api_sign_request(
7
7
  request.query_parameters.select{|key, value| [:public_id, :version].include?(key.to_sym)},
8
8
  Cloudinary.config.api_secret)
9
9
  return received_signature == calculated_signature
10
- end
10
+ end
11
11
  end
12
-
13
- ActionController::Base.send :include, CloudinaryController
@@ -0,0 +1,43 @@
1
+ module Cloudinary
2
+ module Config
3
+ include BaseConfig
4
+
5
+ ENV_URL = "CLOUDINARY_URL"
6
+ SCHEME = "cloudinary"
7
+
8
+ def load_config_from_env
9
+ if ENV["CLOUDINARY_CLOUD_NAME"]
10
+ config_keys = ENV.keys.select! { |key| key.start_with? "CLOUDINARY_" }
11
+ config_keys -= ["CLOUDINARY_URL"] # ignore it when explicit options are passed
12
+ config_keys.each do |full_key|
13
+ conf_key = full_key["CLOUDINARY_".length..-1].downcase # convert "CLOUDINARY_CONFIG_NAME" to "config_name"
14
+ conf_val = ENV[full_key]
15
+ conf_val = conf_val == 'true' if %w[true false].include?(conf_val) # cast relevant boolean values
16
+ update(conf_key => conf_val)
17
+ end
18
+ elsif ENV[ENV_URL]
19
+ load_from_url(ENV[ENV_URL])
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def env_url
26
+ ENV_URL
27
+ end
28
+
29
+ def expected_scheme
30
+ SCHEME
31
+ end
32
+
33
+ def config_from_parsed_url(parsed_url)
34
+ {
35
+ "cloud_name" => parsed_url.host,
36
+ "api_key" => parsed_url.user,
37
+ "api_secret" => parsed_url.password,
38
+ "private_cdn" => !parsed_url.path.blank?,
39
+ "secure_distribution" => parsed_url.path[1..-1]
40
+ }
41
+ end
42
+ end
43
+ end