image_vise 0.0.24 → 0.0.25

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3507edd4073f96af8d398cf699681e34d75408c
4
- data.tar.gz: b77cdb7bde85fd9518c772d44e750cec01075586
3
+ metadata.gz: 4505547a1ef87774694ee0a4e0adda3354e9f676
4
+ data.tar.gz: c403f4147f907b10b2f13ca6ea2564f73bc21303
5
5
  SHA512:
6
- metadata.gz: c1c78c6d74c265588f75820738d69abb4eee377e4b4a2adc97c42e39788daca884e100fdd3af1384f5c3d17727f9a13b17f4afd857445ab14e92efeaedb719c5
7
- data.tar.gz: 4ad00dd86ccac4d4165151814f3c5622e7db92346177c75d8d33311fcc7773f08cd6f1f9fe70522c0a19b5678b0174fa05489103a2110369b611964b51f96675
6
+ metadata.gz: 736ed9bedaf46db49558b3fcec83d992368ccd670df57083be3e8ec66dbfc7bc31420a0debbd085b9237fdb29891b8c495bfbc27d0b6fac7e902b2f3938ef3f5
7
+ data.tar.gz: fe83b1a5f073e2b01075f6ba8f7e4451985727650e69ff390a1abc3350113639c8459e3e1341fcb45738390cf1750671694593edbcfd71bfa0db9802b66c49c7
@@ -0,0 +1,23 @@
1
+ # Anywhere in your app code
2
+ module ImageViseAppsignal
3
+ ImageVise::RenderEngine.prepend self
4
+
5
+ def setup_error_handling(rack_env)
6
+ txn = Appsignal::Transaction.current
7
+ txn.set_action('%s#%s' % [self.class, 'call'])
8
+ end
9
+
10
+ def handle_request_error(err)
11
+ Appsignal.add_exception(err)
12
+ end
13
+
14
+ def handle_generic_error(err)
15
+ Appsignal.add_exception(err)
16
+ end
17
+ end
18
+
19
+ # In config.ru
20
+ map '/thumbnails' do
21
+ use Appsignal::Rack::GenericInstrumentation
22
+ run ImageVise
23
+ end
@@ -0,0 +1,25 @@
1
+ # Anywhere in your app code
2
+ module ImageViseSentrySupport
3
+ ImageVise::RenderEngine.prepend self
4
+
5
+ def setup_error_handling(rack_env)
6
+ @env = rack_env
7
+ end
8
+
9
+ def handle_request_error(err)
10
+ @env['rack.exception'] = err
11
+ end
12
+
13
+ def handle_generic_error(err)
14
+ @env['rack.exception'] = err
15
+ end
16
+ end
17
+
18
+ # In config.ru
19
+ Raven.configure do |config|
20
+ config.dsn = 'https://secretoken@app.getsentry.com/1234567'
21
+ end
22
+ use Raven::Rack
23
+ map '/thumbnails' do
24
+ run ImageVise
25
+ end
data/image_vise.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: image_vise 0.0.24 ruby lib
5
+ # stub: image_vise 0.0.25 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "image_vise"
9
- s.version = "0.0.24"
9
+ s.version = "0.0.25"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Julik Tarkhanov"]
14
- s.date = "2016-10-17"
14
+ s.date = "2016-10-18"
15
15
  s.description = "Image processing via URLs"
16
16
  s.email = "me@julik.nl"
17
17
  s.extra_rdoc_files = [
@@ -24,21 +24,25 @@ Gem::Specification.new do |s|
24
24
  "README.md",
25
25
  "Rakefile",
26
26
  "examples/config.ru",
27
+ "examples/error_handline_appsignal.rb",
28
+ "examples/error_handling_sentry.rb",
27
29
  "image_vise.gemspec",
28
30
  "lib/image_vise.rb",
29
- "lib/image_vise/auto_orient.rb",
30
- "lib/image_vise/crop.rb",
31
- "lib/image_vise/ellipse_stencil.rb",
31
+ "lib/image_vise/fetchers/fetcher_file.rb",
32
+ "lib/image_vise/fetchers/fetcher_http.rb",
32
33
  "lib/image_vise/file_response.rb",
33
- "lib/image_vise/fit_crop.rb",
34
- "lib/image_vise/geom.rb",
35
34
  "lib/image_vise/image_request.rb",
35
+ "lib/image_vise/operators/auto_orient.rb",
36
+ "lib/image_vise/operators/crop.rb",
37
+ "lib/image_vise/operators/ellipse_stencil.rb",
38
+ "lib/image_vise/operators/fit_crop.rb",
39
+ "lib/image_vise/operators/geom.rb",
40
+ "lib/image_vise/operators/sRGB_v4_ICC_preference_displayclass.icc",
41
+ "lib/image_vise/operators/sharpen.rb",
42
+ "lib/image_vise/operators/srgb.rb",
43
+ "lib/image_vise/operators/strip_metadata.rb",
36
44
  "lib/image_vise/pipeline.rb",
37
45
  "lib/image_vise/render_engine.rb",
38
- "lib/image_vise/sRGB_v4_ICC_preference_displayclass.icc",
39
- "lib/image_vise/sharpen.rb",
40
- "lib/image_vise/srgb.rb",
41
- "lib/image_vise/strip_metadata.rb",
42
46
  "spec/image_vise/auto_orient_spec.rb",
43
47
  "spec/image_vise/crop_spec.rb",
44
48
  "spec/image_vise/ellipse_stencil_spec.rb",
@@ -0,0 +1,27 @@
1
+ class ImageVise::FetcherFile
2
+ class AccessError < StandardError
3
+ def http_status; 403; end
4
+ end
5
+ def self.fetch_uri_to_tempfile(uri)
6
+ tf = Tempfile.new 'imagevise-localfs-copy'
7
+ real_path_on_filesystem = File.expand_path(URI.decode(uri.path))
8
+ verify_filesystem_access! real_path_on_filesystem
9
+ # Do the checks
10
+ File.open(real_path_on_filesystem, 'rb') do |f|
11
+ IO.copy_stream(f, tf)
12
+ end
13
+ tf.rewind; tf
14
+ rescue Exception => e
15
+ ImageVise.close_and_unlink(tf)
16
+ raise e
17
+ end
18
+
19
+ def self.verify_filesystem_access!(path_on_filesystem)
20
+ patterns = ImageVise.allowed_filesystem_sources
21
+ matches = patterns.any? { |glob_pattern| File.fnmatch?(glob_pattern, path_on_filesystem) }
22
+ raise AccessError, "filesystem access is disabled" unless patterns.any?
23
+ raise AccessError, "#{src_url} is not on the path whitelist" unless matches
24
+ end
25
+
26
+ ImageVise.register_fetcher 'file', self
27
+ end
@@ -0,0 +1,43 @@
1
+ class ImageVise::FetcherHTTP
2
+ EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS = 5
3
+
4
+ class AccessError < StandardError; end
5
+
6
+ class UpstreamError < StandardError
7
+ attr_accessor :http_status
8
+ def initialize(http_status, message)
9
+ super(message)
10
+ @http_status = http_status
11
+ end
12
+ end
13
+
14
+ def self.fetch_uri_to_tempfile(uri)
15
+ tf = Tempfile.new 'imagevise-http-download'
16
+ verify_uri_access!(uri)
17
+ s = Patron::Session.new
18
+ s.automatic_content_encoding = true
19
+ s.timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
20
+ s.connect_timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
21
+
22
+ response = s.get_file(uri.to_s, tf.path)
23
+
24
+ if response.status != 200
25
+ raise UpstreamError.new(response.status, "Unfortunate upstream response #{response.status}")
26
+ end
27
+
28
+ tf
29
+ rescue Exception => e
30
+ ImageVise.close_and_unlink(tf)
31
+ raise e
32
+ end
33
+
34
+ def self.verify_uri_access!(uri)
35
+ host = uri.host
36
+ unless ImageVise.allowed_hosts.include?(uri.host)
37
+ raise AccessError, "#{uri} is not permitted as source"
38
+ end
39
+ end
40
+
41
+ ImageVise.register_fetcher 'http', self
42
+ ImageVise.register_fetcher 'https', self
43
+ end
@@ -17,7 +17,6 @@ class ImageVise::FileResponse
17
17
  end
18
18
 
19
19
  def close
20
- @file.close
21
- @file.unlink
20
+ ImageVise.close_and_unlink(@file)
22
21
  end
23
22
  end
@@ -6,7 +6,7 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
6
6
 
7
7
  # Initializes a new ParamsChecker from given HTTP server framework
8
8
  # params. The params can be symbol- or string-keyed, does not matter.
9
- def self.to_request(qs_params:, secrets:, permitted_source_hosts:, allowed_filesystem_patterns:)
9
+ def self.to_request(qs_params:, secrets:)
10
10
  base64_encoded_params = qs_params.fetch(:q) rescue qs_params.fetch('q')
11
11
  given_signature = qs_params.fetch(:sig) rescue qs_params.fetch('sig')
12
12
 
@@ -20,22 +20,10 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
20
20
  params = JSON.parse(decoded_json, symbolize_names: true)
21
21
 
22
22
  # Pick up the URL and validate it
23
- src_url = params.fetch(:src_url).to_s
24
- raise URLError, "the :src_url parameter must be non-empty" if src_url.empty?
25
-
26
- src_url = URI.parse(src_url)
27
- if src_url.scheme == 'file'
28
- file_path = URI.decode(src_url.path)
29
- raise URLError, "#{src_url} not permitted since filesystem access is disabled" if allowed_filesystem_patterns.empty?
30
- raise URLError, "#{src_url} is not on the path whitelist" unless allowed_path?(allowed_filesystem_patterns, file_path)
31
- elsif src_url.scheme != 'file'
32
- raise URLError, "#{src_url} is not permitted as source" unless permitted_source_hosts.include?(src_url.host)
33
- end
34
-
35
- # Build out the processing pipeline
23
+ source_url_str = params.fetch(:src_url).to_s
24
+ raise URLError, "the :src_url parameter must be non-empty" if source_url_str.empty?
36
25
  pipeline_definition = params.fetch(:pipeline)
37
-
38
- new(src_url: src_url.to_s, pipeline: ImageVise::Pipeline.from_param(pipeline_definition))
26
+ new(src_url: URI(source_url_str), pipeline: ImageVise::Pipeline.from_param(pipeline_definition))
39
27
  rescue KeyError => e
40
28
  raise InvalidRequest.new(e.message)
41
29
  end
@@ -46,7 +34,7 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
46
34
  end
47
35
 
48
36
  def to_h
49
- {pipeline: pipeline.to_params, src_url: src_url}
37
+ {pipeline: pipeline.to_params, src_url: src_url.to_s}
50
38
  end
51
39
 
52
40
  def cache_etag
@@ -55,11 +43,6 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
55
43
 
56
44
  private
57
45
 
58
- def self.allowed_path?(filesystem_glob_patterns, path_to_check)
59
- expanded_path = File.expand_path(path_to_check)
60
- filesystem_glob_patterns.any? {|pattern| File.fnmatch?(pattern, expanded_path) }
61
- end
62
-
63
46
  def self.valid_signature?(for_payload, given_signature, secrets)
64
47
  # Check the signature against every key that we have,
65
48
  # since different apps might be using different keys
@@ -6,7 +6,7 @@ class ImageVise::Pipeline
6
6
  def self.from_param(array_of_operator_names_to_operator_params)
7
7
  operators = array_of_operator_names_to_operator_params.map do |(operator_name, operator_params)|
8
8
  operator_class = operator_by_name(operator_name)
9
- if operator_params.any? && operator_class.method(:new).arity.nonzero?
9
+ if operator_params && operator_params.any? && operator_class.method(:new).arity.nonzero?
10
10
  operator_class.new(**operator_params)
11
11
  else
12
12
  operator_class.new
@@ -2,9 +2,6 @@ class ImageVise::RenderEngine
2
2
  class UnsupportedInputFormat < StandardError; end
3
3
  class EmptyRender < StandardError; end
4
4
 
5
- # Codes that have to be sent through to the requester
6
- PASSTHROUGH_STATUS_CODES = [404, 403, 503, 504, 500]
7
-
8
5
  DEFAULT_HEADERS = {
9
6
  'Allow' => "GET"
10
7
  }.freeze
@@ -15,6 +12,14 @@ class ImageVise::RenderEngine
15
12
  'Cache-Control' => 'private, max-age=0, no-cache'
16
13
  }).freeze
17
14
 
15
+ # "public" of course. Add max-age so that there is _some_
16
+ # revalidation after a time (otherwise some proxies treat it
17
+ # as "must-revalidate" always), and "no-transform" so that
18
+ # various deflate schemes are not applied to it (does happen
19
+ # with Rack::Cache and leads Chrome to throw up on content
20
+ # decoding for example).
21
+ IMAGE_CACHE_CONTROL = 'public, no-transform, max-age=2592000'
22
+
18
23
  # How long is a render (the ImageMagick/write part) is allowed to
19
24
  # take before we kill it
20
25
  RENDER_TIMEOUT_SECONDS = 10
@@ -30,20 +35,7 @@ class ImageVise::RenderEngine
30
35
  EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS = 4
31
36
 
32
37
  # The default file type for images with alpha
33
- PNG_FILE_TYPE = Class.new do
34
- def self.mime; 'image/png'; end
35
- def self.ext; 'png'; end
36
- end
37
-
38
- # Fetch the given URL into a Tempfile and return the File object
39
- def fetch_url_into_tempfile(source_image_uri)
40
- parsed = URI.parse(source_image_uri)
41
- if parsed.scheme == 'file'
42
- copy_path_into_tempfile(URI.decode(parsed.path))
43
- else
44
- fetch_url(source_image_uri)
45
- end
46
- end
38
+ PNG_FILE_TYPE = MagicBytes::FileType.new('png','image/png').freeze
47
39
 
48
40
  def bail(status, *errors_array)
49
41
  h = JSON_ERROR_HEADERS.dup # Needed because some upstream middleware migh be modifying headers
@@ -69,8 +61,8 @@ class ImageVise::RenderEngine
69
61
  req = Rack::Request.new(env)
70
62
  bail(405, 'Only GET supported') unless req.get?
71
63
 
72
- # Validate the inputs
73
- image_request = ImageVise::ImageRequest.to_request(qs_params: req.params, **image_request_options)
64
+ # Parse and reinstate the URL and pipeline
65
+ image_request = ImageVise::ImageRequest.to_request(qs_params: req.params, secrets: ImageVise.secret_keys)
74
66
 
75
67
  # Recover the source image URL and the pipeline instructions (all the image ops)
76
68
  source_image_uri, pipeline = image_request.src_url, image_request.pipeline
@@ -80,8 +72,9 @@ class ImageVise::RenderEngine
80
72
  # Assume the image URL contents does _never_ change.
81
73
  etag = image_request.cache_etag
82
74
 
83
- # Download the original into a Tempfile
84
- source_file = fetch_url_into_tempfile(source_image_uri)
75
+ # Download/copy the original into a Tempfile
76
+ fetcher = ImageVise.fetcher_for(source_image_uri.scheme)
77
+ source_file = fetcher.fetch_uri_to_tempfile(source_image_uri)
85
78
 
86
79
  # Make sure we do not try to process something...questionable
87
80
  source_file_type = detect_file_type(source_file)
@@ -106,7 +99,7 @@ class ImageVise::RenderEngine
106
99
  response_headers = DEFAULT_HEADERS.merge({
107
100
  'Content-Type' => render_file_type.mime,
108
101
  'Content-Length' => '%d' % render_destination_file.size,
109
- 'Cache-Control' => 'public, no-transform, max-age=2592000',
102
+ 'Cache-Control' => IMAGE_CACHE_CONTROL,
110
103
  'ETag' => etag
111
104
  })
112
105
 
@@ -115,12 +108,18 @@ class ImageVise::RenderEngine
115
108
  [200, response_headers, ImageVise::FileResponse.new(render_destination_file)]
116
109
  rescue *permanent_failures => e
117
110
  handle_request_error(e)
118
- raise_exception_or_error_response(e, 422)
111
+ http_status_code = e.respond_to?(:http_status) ? e.http_status : 422
112
+ raise_exception_or_error_response(e, http_status_code)
119
113
  rescue Exception => e
120
- handle_generic_error(e)
121
- raise_exception_or_error_response(e, 500)
114
+ if http_status_code = (e.respond_to?(:http_status) && e.http_status)
115
+ handle_request_error(e)
116
+ raise_exception_or_error_response(e, http_status_code)
117
+ else
118
+ handle_generic_error(e)
119
+ raise_exception_or_error_response(e, 500)
120
+ end
122
121
  ensure
123
- close_and_unlink(source_file)
122
+ ImageVise.close_and_unlink(source_file)
124
123
  end
125
124
 
126
125
  def raise_exception_or_error_response(exception, status_code)
@@ -131,12 +130,6 @@ class ImageVise::RenderEngine
131
130
  end
132
131
  end
133
132
 
134
- def close_and_unlink(f)
135
- return unless f
136
- f.close unless f.closed?
137
- f.unlink
138
- end
139
-
140
133
  def binary_tempfile
141
134
  Tempfile.new('imagevise-tmp').tap{|f| f.binmode }
142
135
  end
@@ -207,44 +200,4 @@ class ImageVise::RenderEngine
207
200
  ImageVise.destroy(magick_image)
208
201
  end
209
202
 
210
- def image_request_options
211
- {
212
- secrets: ImageVise.secret_keys,
213
- permitted_source_hosts: ImageVise.allowed_hosts,
214
- allowed_filesystem_patterns: ImageVise.allowed_filesystem_sources,
215
- }
216
- end
217
-
218
- def fetch_url(source_image_uri)
219
- tf = binary_tempfile
220
- s = Patron::Session.new
221
- s.automatic_content_encoding = true
222
- s.timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
223
- s.connect_timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
224
- response = s.get_file(source_image_uri, tf.path)
225
- if PASSTHROUGH_STATUS_CODES.include?(response.status)
226
- tf.close; tf.unlink;
227
- bail response.status, "Unfortunate upstream response: #{response.status}"
228
- end
229
- tf
230
- rescue Exception => e
231
- tf.close; tf.unlink;
232
- raise e
233
- end
234
-
235
- def copy_path_into_tempfile(path_on_filesystem)
236
- tf = binary_tempfile
237
- real_path_on_filesystem = File.expand_path(path_on_filesystem)
238
- File.open(real_path_on_filesystem, 'rb') do |f|
239
- IO.copy_stream(f, tf)
240
- end
241
- tf.rewind; tf
242
- rescue Errno::ENOENT
243
- tf.close; tf.unlink;
244
- bail 404, "Image file not found"
245
- rescue Exception => e
246
- tf.close; tf.unlink;
247
- raise e
248
- end
249
-
250
203
  end
data/lib/image_vise.rb CHANGED
@@ -8,7 +8,7 @@ require 'base64'
8
8
  require 'rack'
9
9
 
10
10
  class ImageVise
11
- VERSION = '0.0.24'
11
+ VERSION = '0.0.25'
12
12
  S_MUTEX = Mutex.new
13
13
  private_constant :S_MUTEX
14
14
 
@@ -16,7 +16,8 @@ class ImageVise
16
16
  @keys = Set.new
17
17
  @operators = {}
18
18
  @allowed_glob_patterns = Set.new
19
-
19
+ @fetchers = {}
20
+
20
21
  class << self
21
22
  # Resets all allowed hosts
22
23
  def reset_allowed_hosts!
@@ -80,7 +81,7 @@ class ImageVise
80
81
  p = Pipeline.new
81
82
  yield(p)
82
83
  raise ArgumentError, "Image pipeline has no steps defined" if p.empty?
83
- ImageRequest.new(src_url: src_url, pipeline: p).to_query_string_params(secret)
84
+ ImageRequest.new(src_url: URI(src_url), pipeline: p).to_query_string_params(secret)
84
85
  end
85
86
 
86
87
  # Adds an operator
@@ -97,8 +98,18 @@ class ImageVise
97
98
  @operators.keys
98
99
  end
99
100
 
101
+ def register_fetcher(scheme, fetcher)
102
+ S_MUTEX.synchronize { @fetchers[scheme.to_s] = fetcher }
103
+ end
104
+
105
+ def fetcher_for(scheme)
106
+ S_MUTEX.synchronize { @fetchers[scheme.to_s] or raise "No fetcher registered for #{scheme}" }
107
+ end
108
+
100
109
  def operator_name_for(operator)
101
- @operators.key(operator.class) or raise "Operator #{operator.inspect} not registered using ImageVise.add_operator"
110
+ S_MUTEX.synchronize do
111
+ @operators.key(operator.class) or raise "Operator #{operator.inspect} not registered using ImageVise.add_operator"
112
+ end
102
113
  end
103
114
  end
104
115
 
@@ -131,6 +142,15 @@ class ImageVise
131
142
  return if maybe_image.destroyed?
132
143
  maybe_image.destroy!
133
144
  end
145
+
146
+ # Used as a shorthand to force-dealloc Tempfiles in an ensure() blocks. Since
147
+ # ensure blocks sometimes deal with variables in inconsistent states (variable
148
+ # in scope but not yet set to an image) we take the possibility of nils into account.
149
+ def self.close_and_unlink(maybe_tempfile)
150
+ return unless maybe_tempfile
151
+ maybe_tempfile.close unless maybe_tempfile.closed?
152
+ maybe_tempfile.unlink
153
+ end
134
154
  end
135
155
 
136
156
  Dir.glob(__dir__ + '/**/*.rb').sort.each do |f|
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ImageVise::ImageRequest do
4
- it 'accepts a set of params, secrets and permitted hosts and returns a Pipeline' do
4
+ it 'accepts a set of params and secrets, and returns a Pipeline' do
5
5
  img_params = {src_url: 'http://bucket.s3.aws.com/image.jpg', pipeline: [[:crop, {width: 10, height: 10, gravity: 's'}]]}
6
6
  img_params_json = JSON.dump(img_params)
7
7
  signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', img_params_json)
@@ -10,31 +10,14 @@ describe ImageVise::ImageRequest do
10
10
  sig: signature
11
11
  }
12
12
 
13
- image_request = described_class.to_request(qs_params: params, secrets: ['this is a secret'],
14
- permitted_source_hosts: ['bucket.s3.aws.com'], allowed_filesystem_patterns: [])
13
+ image_request = described_class.to_request(qs_params: params, secrets: ['this is a secret'])
15
14
  request_qs_params = image_request.to_query_string_params('this is a secret')
16
15
  expect(request_qs_params).to be_kind_of(Hash)
17
16
 
18
- image_request_roundtrip = described_class.to_request(qs_params: request_qs_params,
19
- secrets: ['this is a secret'], permitted_source_hosts: ['bucket.s3.aws.com'], allowed_filesystem_patterns: [])
17
+ image_request_roundtrip = described_class.to_request(qs_params: request_qs_params, secrets: ['this is a secret'])
20
18
  end
21
19
 
22
- it 'forbids a file:// URL if the flag is not enabled' do
23
- img_params = {src_url: 'file:///etc/passwd', pipeline: [[:auto_orient]]}
24
- img_params_json = JSON.dump(img_params)
25
- signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', img_params_json)
26
- params = {
27
- q: Base64.encode64(img_params_json),
28
- sig: signature
29
- }
30
-
31
- expect {
32
- described_class.to_request(qs_params: params, secrets: ['this is a secret'],
33
- permitted_source_hosts: ['bucket.s3.aws.com'], allowed_filesystem_patterns: [])
34
- }.to raise_error(/filesystem access is disabled/)
35
- end
36
-
37
- it 'allows a file:// URL if its path is within the permit list' do
20
+ it 'converts a file:// URL into a URI objectlist' do
38
21
  img_params = {src_url: 'file:///etc/passwd', pipeline: [[:auto_orient, {}]]}
39
22
  img_params_json = JSON.dump(img_params)
40
23
  signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', img_params_json)
@@ -42,11 +25,8 @@ describe ImageVise::ImageRequest do
42
25
  q: Base64.encode64(img_params_json),
43
26
  sig: signature
44
27
  }
45
-
46
- image_request = described_class.to_request(qs_params: params, secrets: ['this is a secret'],
47
- permitted_source_hosts: ['bucket.s3.aws.com'], allowed_filesystem_patterns: %w( /etc/* ))
48
- request_qs_params = image_request.to_query_string_params('this is a secret')
49
- expect(request_qs_params).to be_kind_of(Hash)
28
+ image_request = described_class.to_request(qs_params: params, secrets: ['this is a secret'])
29
+ expect(image_request.src_url).to be_kind_of(URI)
50
30
  end
51
31
 
52
32
  describe 'fails with an invalid pipeline' do
@@ -59,8 +39,6 @@ describe ImageVise::ImageRequest do
59
39
  describe 'fails with an invalid URL' do
60
40
  it 'when the URL param is missing'
61
41
  it 'when the URL param is empty'
62
- it 'when the URL is referencing a non-permitted host'
63
- it 'when the URL refers to a non-HTTP(S) scheme'
64
42
  end
65
43
 
66
44
  describe 'fails with an invalid signature' do
@@ -77,8 +55,7 @@ describe ImageVise::ImageRequest do
77
55
  }
78
56
 
79
57
  expect {
80
- described_class.to_request(qs_params: params,
81
- secrets: ['b'], permitted_source_hosts: ['bucket.s3.aws.com'], allowed_filesystem_patterns: [])
58
+ described_class.to_request(qs_params: params, secrets: ['b'])
82
59
  }.to raise_error(/Invalid or missing signature/)
83
60
  end
84
61
  end
@@ -72,7 +72,7 @@ describe ImageVise::RenderEngine do
72
72
  params = image_request.to_query_string_params('l33tness')
73
73
 
74
74
  get '/', params
75
- expect(last_response.status).to eq(422)
75
+ expect(last_response.status).to eq(403)
76
76
  expect(last_response.body).to include('filesystem access is disabled')
77
77
  end
78
78
 
@@ -90,7 +90,7 @@ describe ImageVise::RenderEngine do
90
90
  expect(last_response.status).to eq(403)
91
91
  expect(last_response.headers['Content-Type']).to eq('application/json')
92
92
  parsed = JSON.load(last_response.body)
93
- expect(parsed['errors']).to include("Unfortunate upstream response: 403")
93
+ expect(parsed['errors'].to_s).to include("Unfortunate upstream response")
94
94
  end
95
95
 
96
96
  it 'replays upstream error response codes that are selected to be replayed to the requester' do
@@ -112,7 +112,7 @@ describe ImageVise::RenderEngine do
112
112
 
113
113
  expect(last_response.headers['Content-Type']).to eq('application/json')
114
114
  parsed = JSON.load(last_response.body)
115
- expect(parsed['errors']).to include("Unfortunate upstream response: #{error_code}")
115
+ expect(parsed['errors'].to_s).to include("Unfortunate upstream response")
116
116
  end
117
117
  end
118
118
 
@@ -63,7 +63,20 @@ describe ImageVise do
63
63
  expect(params[:sig]).not_to be_empty
64
64
  end
65
65
  end
66
-
66
+
67
+ describe 'methods dealing with fetchers' do
68
+ it 'returns the fetchers for the default schemes' do
69
+ http = ImageVise.fetcher_for('http')
70
+ expect(http).to respond_to(:fetch_uri_to_tempfile)
71
+ file = ImageVise.fetcher_for('file')
72
+ expect(http).to respond_to(:fetch_uri_to_tempfile)
73
+
74
+ expect {
75
+ ImageVise.fetcher_for('undernet')
76
+ }.to raise_error(/No fetcher registered/)
77
+ end
78
+ end
79
+
67
80
  describe 'methods dealing with the operator list' do
68
81
  it 'have the basic operators already set up' do
69
82
  oplist = ImageVise.defined_operator_names
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_vise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.24
4
+ version: 0.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-17 00:00:00.000000000 Z
11
+ date: 2016-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -253,21 +253,25 @@ files:
253
253
  - README.md
254
254
  - Rakefile
255
255
  - examples/config.ru
256
+ - examples/error_handline_appsignal.rb
257
+ - examples/error_handling_sentry.rb
256
258
  - image_vise.gemspec
257
259
  - lib/image_vise.rb
258
- - lib/image_vise/auto_orient.rb
259
- - lib/image_vise/crop.rb
260
- - lib/image_vise/ellipse_stencil.rb
260
+ - lib/image_vise/fetchers/fetcher_file.rb
261
+ - lib/image_vise/fetchers/fetcher_http.rb
261
262
  - lib/image_vise/file_response.rb
262
- - lib/image_vise/fit_crop.rb
263
- - lib/image_vise/geom.rb
264
263
  - lib/image_vise/image_request.rb
264
+ - lib/image_vise/operators/auto_orient.rb
265
+ - lib/image_vise/operators/crop.rb
266
+ - lib/image_vise/operators/ellipse_stencil.rb
267
+ - lib/image_vise/operators/fit_crop.rb
268
+ - lib/image_vise/operators/geom.rb
269
+ - lib/image_vise/operators/sRGB_v4_ICC_preference_displayclass.icc
270
+ - lib/image_vise/operators/sharpen.rb
271
+ - lib/image_vise/operators/srgb.rb
272
+ - lib/image_vise/operators/strip_metadata.rb
265
273
  - lib/image_vise/pipeline.rb
266
274
  - lib/image_vise/render_engine.rb
267
- - lib/image_vise/sRGB_v4_ICC_preference_displayclass.icc
268
- - lib/image_vise/sharpen.rb
269
- - lib/image_vise/srgb.rb
270
- - lib/image_vise/strip_metadata.rb
271
275
  - spec/image_vise/auto_orient_spec.rb
272
276
  - spec/image_vise/crop_spec.rb
273
277
  - spec/image_vise/ellipse_stencil_spec.rb
File without changes
File without changes
File without changes