cloudinary 1.9.1 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
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