image_vise 0.0.24 → 0.0.25

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 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