image_vise 0.5.0 → 0.6.0

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
- SHA256:
3
- metadata.gz: b65aacfbdbada4bb3d52c9883955ff2204ee84aa66bc82d05fc81320259ac900
4
- data.tar.gz: d8576cfba12f8793a75dc36f8a5c2c206f7650f402d048432f763de82ddeb0b0
2
+ SHA1:
3
+ metadata.gz: 1be0d4d8682d0436356b4ea5e0023869b50709dd
4
+ data.tar.gz: de463d93ee8e7e55c0cfa59db9a5c0dee9b91a25
5
5
  SHA512:
6
- metadata.gz: 767f28b82f290cfefdf023f83a6794901744d585eff9f0f2af92a6c408718b4663162156270d51f0ea7def4de2e14339a0bc0183b80ba62d4d16f11b69359e11
7
- data.tar.gz: bb8534e292d19e51015b24586d0db5c472da2b02c3463180c35f42d8cff1d2d39aca875d5446255ea5b67b1bf59f849dda08065792438324a6ed3c9a753d9549
6
+ metadata.gz: e93df7c9c8723663ab09a7ba09916f5327ec7b2a71e63818b7e4bf72d851dcb119fd21c9133bd6e97158fc672e9ac64043db9f6f897a5dd9b051dd7e706c5315
7
+ data.tar.gz: 15e293b3e33026a78f486ba2921a8df5a1af1b51fb08c3984864b81316697c14979715148e39eaa00f0b85a7228cc0a79f19a7b8f41002558f6e86ceaec507cd
data/SECURITY.md CHANGED
@@ -31,9 +31,9 @@ CDN cache because the query string params contain extra data.
31
31
 
32
32
  ## Protection for remote URLs from HTTP(s) origins
33
33
 
34
- Only URLs referring to permitted hosts are going to be permitted for fetching. If there are no hosts added,
34
+ Only URLs on whitelisted hosts are going to be fetched. If there are no hosts added,
35
35
  any remote URL is going to cause an exception. No special verification for whether the upstream must be HTTP
36
- or HTTPS is performed at this time.
36
+ or HTTPS is performed at this time, but HTTPS upstreams' SSL certificats _will_ be verified.
37
37
 
38
38
  ## Protection for "file:/" URLs
39
39
 
data/image_vise.gemspec CHANGED
@@ -27,12 +27,13 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency 'patron', '~> 0.6'
30
+ spec.add_dependency 'patron', '~> 0.9'
31
31
  spec.add_dependency 'rmagick', '~> 2.15'
32
32
  spec.add_dependency 'ks'
33
- spec.add_dependency 'magic_bytes', '~> 1'
34
33
  spec.add_dependency 'rack', '>= 1', '< 3'
34
+ spec.add_dependency 'format_parser', '>= 0.12.1', '< 1.0'
35
35
 
36
+ spec.add_development_dependency 'magic_bytes', '~> 1'
36
37
  spec.add_development_dependency "bundler", "~> 1.7"
37
38
  spec.add_development_dependency "rake", "~> 12.2"
38
39
  spec.add_development_dependency "rack-test"
data/lib/image_vise.rb CHANGED
@@ -6,6 +6,7 @@ require 'magic_bytes'
6
6
  require 'thread'
7
7
  require 'base64'
8
8
  require 'rack'
9
+ require 'format_parser'
9
10
 
10
11
  class ImageVise
11
12
  require_relative 'image_vise/version'
@@ -16,6 +17,10 @@ class ImageVise
16
17
  # The default cache liftime is 30 days, and will be used if no custom lifetime is set.
17
18
  DEFAULT_CACHE_LIFETIME = 2_592_000
18
19
 
20
+ # The default limit on how large may a file loaded for processing be, in bytes. This
21
+ # is in addition to the constraints on the file format.
22
+ DEFAULT_MAXIMUM_SOURCE_FILE_SIZE = 48 * 1024 * 1024
23
+
19
24
  @allowed_hosts = Set.new
20
25
  @keys = Set.new
21
26
  @operators = {}
@@ -172,7 +177,7 @@ class ImageVise
172
177
  return unless maybe_tempfile
173
178
  ImageVise::Measurometer.instrument('image_vise.tempfile_unlink') do
174
179
  maybe_tempfile.close unless maybe_tempfile.closed?
175
- maybe_tempfile.unlink
180
+ maybe_tempfile.unlink if maybe_tempfile.respond_to?(:unlink)
176
181
  end
177
182
  end
178
183
  end
@@ -2,11 +2,16 @@ class ImageVise::FetcherFile
2
2
  class AccessError < StandardError
3
3
  def http_status; 403; end
4
4
  end
5
+
6
+ class SizeError < AccessError
7
+ def http_status; 400; end
8
+ end
9
+
5
10
  def self.fetch_uri_to_tempfile(uri)
6
11
  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
12
+ real_path_on_filesystem = uri_to_path(uri)
13
+ verify_filesystem_access!(real_path_on_filesystem)
14
+ verify_file_size_within_limit!(real_path_on_filesystem)
10
15
  File.open(real_path_on_filesystem, 'rb') do |f|
11
16
  IO.copy_stream(f, tf)
12
17
  end
@@ -16,6 +21,10 @@ class ImageVise::FetcherFile
16
21
  raise e
17
22
  end
18
23
 
24
+ def self.uri_to_path(uri)
25
+ File.expand_path(URI.decode(uri.path))
26
+ end
27
+
19
28
  def self.verify_filesystem_access!(path_on_filesystem)
20
29
  patterns = ImageVise.allowed_filesystem_sources
21
30
  matches = patterns.any? { |glob_pattern| File.fnmatch?(glob_pattern, path_on_filesystem) }
@@ -23,5 +32,16 @@ class ImageVise::FetcherFile
23
32
  raise AccessError, "#{path_on_filesystem} is not on the path whitelist" unless matches
24
33
  end
25
34
 
35
+ def self.verify_file_size_within_limit!(path_on_filesystem)
36
+ file_size = File.size(path_on_filesystem)
37
+ if file_size > maximum_source_file_size_bytes
38
+ raise SizeError, "#{path_on_filesystem} is too large to process (#{file_size} bytes)"
39
+ end
40
+ end
41
+
42
+ def self.maximum_source_file_size_bytes
43
+ ImageVise::DEFAULT_MAXIMUM_SOURCE_FILE_SIZE
44
+ end
45
+
26
46
  ImageVise.register_fetcher 'file', self
27
47
  end
@@ -14,23 +14,35 @@ class ImageVise::FetcherHTTP
14
14
  def self.fetch_uri_to_tempfile(uri)
15
15
  tf = Tempfile.new 'imagevise-http-download'
16
16
  verify_uri_access!(uri)
17
+
17
18
  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
-
19
+ configure_patron_session!(s)
22
20
  response = s.get_file(uri.to_s, tf.path)
23
-
21
+
24
22
  if response.status != 200
25
23
  raise UpstreamError.new(response.status, "Unfortunate upstream response #{response.status} on #{uri}")
26
24
  end
27
-
25
+
28
26
  tf
27
+ rescue Patron::Aborted # File size exceeds permitted size
28
+ ImageVise.close_and_unlink(tf)
29
+ raise UpstreamError.new(400, "Upstream resource at #{uri} is too large to load")
29
30
  rescue Exception => e
30
31
  ImageVise.close_and_unlink(tf)
31
32
  raise e
32
33
  end
33
-
34
+
35
+ def self.maximum_response_size_bytes
36
+ ImageVise::DEFAULT_MAXIMUM_SOURCE_FILE_SIZE
37
+ end
38
+
39
+ def self.configure_patron_session!(session)
40
+ session.automatic_content_encoding = true
41
+ session.timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
42
+ session.connect_timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
43
+ session.download_byte_limit = maximum_response_size_bytes
44
+ end
45
+
34
46
  def self.verify_uri_access!(uri)
35
47
  host = uri.host
36
48
  return if ImageVise.allowed_hosts.include?(uri.host)
@@ -2,6 +2,16 @@
2
2
  class UnsupportedInputFormat < StandardError; end
3
3
  class EmptyRender < StandardError; end
4
4
 
5
+ class Filetype < Struct.new(:format_parser_format)
6
+ def mime
7
+ Rack::Mime.mime_type(ext)
8
+ end
9
+
10
+ def ext
11
+ ".#{format_parser_format}"
12
+ end
13
+ end
14
+
5
15
  DEFAULT_HEADERS = {
6
16
  'Allow' => "GET"
7
17
  }.freeze
@@ -28,12 +38,8 @@
28
38
  # decoding for example).
29
39
  IMAGE_CACHE_CONTROL = "public, no-transform, max-age=%d"
30
40
 
31
- # How long is a render (the ImageMagick/write part) is allowed to
32
- # take before we kill it
33
- RENDER_TIMEOUT_SECONDS = 10
34
-
35
- # Which input files we permit (based on extensions stored in MagicBytes)
36
- PERMITTED_SOURCE_FILE_EXTENSIONS = %w( gif png jpg psd tif)
41
+ # Which input files we permit (based on format identifiers in format_parser, which are symbols)
42
+ PERMITTED_SOURCE_FORMATS = [:bmp, :tif, :jpg, :psd, :gif, :png]
37
43
 
38
44
  # How long should we wait when fetching the image from the external host
39
45
  EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS = 4
@@ -85,7 +91,7 @@
85
91
  handle_request_error(e)
86
92
  http_status_code = e.respond_to?(:http_status) ? e.http_status : 400
87
93
  raise_exception_or_error_response(e, http_status_code)
88
- rescue Exception => e
94
+ rescue => e
89
95
  if http_status_code = (e.respond_to?(:http_status) && e.http_status)
90
96
  handle_request_error(e)
91
97
  raise_exception_or_error_response(e, http_status_code)
@@ -134,7 +140,7 @@
134
140
  # representing the processing pipeline
135
141
  #
136
142
  # @param image_request[ImageVise::ImageRequest] the request for the image
137
- # @return [Array<File, MagicBytes::FileType, String]
143
+ # @return [Array<File, FileType, String]
138
144
  def process_image_request(image_request)
139
145
  # Recover the source image URL and the pipeline instructions (all the image ops)
140
146
  source_image_uri, pipeline = image_request.src_url, image_request.pipeline
@@ -146,24 +152,26 @@
146
152
 
147
153
  # Download/copy the original into a Tempfile
148
154
  fetcher = ImageVise.fetcher_for(source_image_uri.scheme)
149
- source_file = fetcher.fetch_uri_to_tempfile(source_image_uri)
150
-
151
- # Make sure we do not try to process something...questionable
152
- source_file_type = detect_file_type(source_file)
153
- unless source_file_type_permitted?(source_file_type)
154
- raise UnsupportedInputFormat.new("Unsupported/unknown input file format .%s" % source_file_type.ext)
155
+ source_file = ImageVise::Measurometer.instrument('image_vise.fetch') do
156
+ fetcher.fetch_uri_to_tempfile(source_image_uri)
155
157
  end
158
+ file_format = FormatParser.parse(source_file, natures: [:image]).tap { source_file.rewind }
159
+ raise UnsupportedInputFormat.new("%s has an unknown input file format" % source_image_uri) unless file_format
160
+ raise UnsupportedInputFormat.new("%s does not pass file constraints" % source_image_uri) unless permitted_format?(file_format)
156
161
 
157
162
  render_destination_file = Tempfile.new('imagevise-render').tap{|f| f.binmode }
158
163
 
159
164
  # Do the actual imaging stuff
160
- expire_after = apply_pipeline(source_file.path, pipeline, source_file_type, render_destination_file.path)
165
+ expire_after = ImageVise::Measurometer.instrument('image_vise.render_engine.apply_pipeline') do
166
+ apply_pipeline(source_file.path, pipeline, file_format, render_destination_file.path)
167
+ end
161
168
 
162
169
  # Catch this one early
163
170
  render_destination_file.rewind
164
171
  raise EmptyRender, "The rendered image was empty" if render_destination_file.size.zero?
165
172
 
166
173
  render_file_type = detect_file_type(render_destination_file)
174
+
167
175
  [render_destination_file, render_file_type, etag, expire_after]
168
176
  ensure
169
177
  ImageVise.close_and_unlink(source_file)
@@ -210,18 +218,23 @@
210
218
  # the MIME type.
211
219
  #
212
220
  # @param tempfile[File] the file to perform detection on
213
- # @return [MagicBytes::FileType] the detected file type
221
+ # @return [Symbol] the detected file format symbol that can be used as an extension
214
222
  def detect_file_type(tempfile)
215
223
  tempfile.rewind
216
- MagicBytes.read_and_detect(tempfile).tap { tempfile.rewind }
224
+ parser_result = FormatParser.parse(tempfile, natures: :image).tap { tempfile.rewind }
225
+ raise "Rendered file type detection failed" unless parser_result
226
+ Filetype.new(parser_result.format)
217
227
  end
218
228
 
219
- # Tells whether the given file type may be loaded into the image processor.
229
+ # Tells whether the file described by the given FormatParser result object
230
+ # can be accepted for processing
220
231
  #
221
- # @param magic_bytes_file_info[MagicBytes::FileType] the filetype
232
+ # @param format_parser_result[FormatParser::Image] file information descriptor
222
233
  # @return [Boolean]
223
- def source_file_type_permitted?(magic_bytes_file_info)
224
- PERMITTED_SOURCE_FILE_EXTENSIONS.include?(magic_bytes_file_info.ext)
234
+ def permitted_format?(format_parser_result)
235
+ return false unless PERMITTED_SOURCE_FORMATS.include?(format_parser_result.format)
236
+ return false if format_parser_result.has_multiple_frames
237
+ true
225
238
  end
226
239
 
227
240
  # Lists exceptions that should lead to the request being flagged
@@ -279,8 +292,7 @@
279
292
  # @param pipeline[#apply!(Magick::Image)] the processing pipeline
280
293
  # @param render_to_path[String] the path to write the rendered image to
281
294
  # @return [void]
282
- def apply_pipeline(source_file_path, pipeline, source_file_type, render_to_path)
283
- render_file_type = source_file_type
295
+ def apply_pipeline(source_file_path, pipeline, source_format_parser_result, render_to_path)
284
296
 
285
297
  # Load the first frame of the animated GIF _or_ the blended compatibility layer from Photoshop
286
298
  image_list = ImageVise::Measurometer.instrument('image_vise.load_pixbuf') do
@@ -290,7 +302,7 @@
290
302
  magick_image = image_list.first # Picks up the "precomp" PSD layer in compatibility mode, or the first frame of a GIF
291
303
 
292
304
  # If any operators want to stash some data for downstream use we use this Hash
293
- metadata = {}
305
+ metadata = {format_parser_result: source_format_parser_result}
294
306
 
295
307
  # Apply the pipeline (all the image operators)
296
308
  pipeline.apply!(magick_image, metadata)
@@ -1,3 +1,3 @@
1
1
  class ImageVise
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -3,21 +3,13 @@
3
3
  # be chosen. Since ImageVise URLs do not contain a file extension we are free to pick
4
4
  # the suitable format at render time
5
5
  class ImageVise::AutoWriter
6
- # The default file type for images with alpha
7
- PNG_FILE_TYPE = MagicBytes::FileType.new('png','image/png').freeze
8
-
9
- # Renders the file as a jpg if the custom output filetype operator is used
10
- JPG_FILE_TYPE = MagicBytes::FileType.new('jpg','image/jpeg').freeze
11
-
6
+ PNG_EXT = 'png'
7
+ JPG_EXT = 'jpg'
12
8
  def write_image!(magick_image, _, render_to_path)
13
9
  # If processing the image has created an alpha channel, use PNG always.
14
10
  # Otherwise, keep the original format for as far as the supported formats list goes.
15
- render_file_type = if magick_image.alpha?
16
- PNG_FILE_TYPE
17
- else
18
- JPG_FILE_TYPE
19
- end
20
- magick_image.format = render_file_type.ext
11
+ extension = magick_image.alpha? ? PNG_EXT : JPG_EXT
12
+ magick_image.format = extension
21
13
  magick_image.write(render_to_path)
22
14
  end
23
15
  end
@@ -1,9 +1,9 @@
1
1
  class ImageVise::JPGWriter < Ks.strict(:quality)
2
- JPG_FILE_TYPE = MagicBytes::FileType.new('jpg','image/jpeg').freeze
2
+ JPG_EXT = 'jpg'
3
3
 
4
4
  def write_image!(magick_image, _, render_to_path)
5
5
  q = self.quality # to avoid the changing "self" context
6
- magick_image.format = JPG_FILE_TYPE.ext
6
+ magick_image.format = JPG_EXT
7
7
  magick_image.write(render_to_path) { self.quality = q }
8
8
  end
9
9
  end
@@ -32,7 +32,7 @@ describe ImageVise::FetcherFile do
32
32
  ImageVise::FetcherFile.fetch_uri_to_tempfile(uri)
33
33
  }.to raise_error(ImageVise::FetcherFile::AccessError)
34
34
  end
35
-
35
+
36
36
  it 'raises a meaningful exception if this file is not permitted as source' do
37
37
  path = File.expand_path(__FILE__)
38
38
 
@@ -45,4 +45,21 @@ describe ImageVise::FetcherFile do
45
45
  ImageVise::FetcherFile.fetch_uri_to_tempfile(uri)
46
46
  }.to raise_error(ImageVise::FetcherFile::AccessError)
47
47
  end
48
+
49
+ it 'raises a meaningful exception if the image exceeds the maximum permitted size' do
50
+ path = File.expand_path(__FILE__)
51
+ ruby_files_in_this_directory = __dir__ + '/*.rb'
52
+ ImageVise.allow_filesystem_source! ruby_files_in_this_directory
53
+
54
+ uri = URI('file://' + URI.encode(path))
55
+ expect(ImageVise::FetcherFile).to receive(:maximum_source_file_size_bytes).and_return(1)
56
+
57
+ expect {
58
+ ImageVise::FetcherFile.fetch_uri_to_tempfile(uri)
59
+ }.to raise_error {|e|
60
+ expect(e).to be_kind_of(ImageVise::FetcherFile::AccessError)
61
+ expect(e.message).to match(/is too large to process/)
62
+ expect(e.http_status).to eq(400)
63
+ }
64
+ end
48
65
  end
@@ -31,6 +31,21 @@ describe ImageVise::FetcherHTTP do
31
31
  }
32
32
  end
33
33
 
34
+ it 'raises an error if the image exceeds the maximum permitted size' do
35
+ uri = URI(public_url_psd)
36
+ ImageVise.add_allowed_host! 'localhost'
37
+ expect(ImageVise::FetcherHTTP).to receive(:maximum_response_size_bytes).and_return(10)
38
+
39
+ expect {
40
+ ImageVise::FetcherHTTP.fetch_uri_to_tempfile(uri)
41
+ }.to raise_error {|e|
42
+ expect(e).to be_kind_of(ImageVise::FetcherHTTP::UpstreamError)
43
+ expect(e.message).to include(uri.to_s)
44
+ expect(e.message).to match(/is too large to load/)
45
+ expect(e.http_status).to eq(400)
46
+ }
47
+ end
48
+
34
49
  it 'fetches the image into a Tempfile' do
35
50
  uri = URI(public_url_psd)
36
51
  ImageVise.add_allowed_host! 'localhost'
@@ -51,16 +51,15 @@ describe ImageVise::RenderEngine do
51
51
  p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
52
52
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
53
53
 
54
- expect_any_instance_of(Patron::Session).to receive(:get_file) {|_self, url, path|
55
- File.open(path, 'wb') {|f| f << 'totally not an image' }
56
- double(status: 200)
57
- }
54
+ bad_data = StringIO.new('totally not an image')
55
+ expect(ImageVise::FetcherHTTP).to receive(:fetch_uri_to_tempfile).and_return(bad_data)
58
56
  expect(app).to receive(:handle_request_error).and_call_original
59
57
 
60
58
  get image_request.to_path_params('l33tness')
59
+
61
60
  expect(last_response.status).to eq(400)
62
61
  expect(last_response['Cache-Control']).to match(/public/)
63
- expect(last_response.body).to include('Unsupported/unknown')
62
+ expect(last_response.body).to include('unknown')
64
63
  end
65
64
 
66
65
  it 'halts with 400 when a file:// URL is given and filesystem access is not enabled' do
@@ -224,7 +223,7 @@ describe ImageVise::RenderEngine do
224
223
  expect(app).to receive(:process_image_request).and_call_original
225
224
  expect(app).to receive(:extract_params_from_request).and_call_original
226
225
  expect(app).to receive(:image_rack_response).and_call_original
227
- expect(app).to receive(:source_file_type_permitted?).and_call_original
226
+ expect(app).to receive(:permitted_format?).and_call_original
228
227
 
229
228
  get image_request.to_path_params('l33tness')
230
229
  expect(last_response.status).to eq(200)
@@ -102,6 +102,39 @@ describe ImageVise do
102
102
  end
103
103
  end
104
104
 
105
+ describe '.close_and_unlink' do
106
+ it 'closes and unlinks a Tempfile' do
107
+ tf = Tempfile.new('x')
108
+ tf << "foo"
109
+ expect(tf).to receive(:close).and_call_original
110
+ expect(tf).to receive(:unlink).and_call_original
111
+
112
+ ImageVise.close_and_unlink(tf)
113
+
114
+ expect(tf).to be_closed
115
+ end
116
+
117
+ it 'unlinks a closed Tempfile' do
118
+ tf = Tempfile.new('x')
119
+ tf << "foo"
120
+ tf.close
121
+ expect(tf).to receive(:unlink).and_call_original
122
+
123
+ ImageVise.close_and_unlink(tf)
124
+ end
125
+
126
+ it 'works on a nil since it gets used in ensure blocks, where the variable might be empty' do
127
+ ImageVise.close_and_unlink(nil) # Should not raise anything
128
+ end
129
+
130
+ it 'works for a StringIO which does not have unlink' do
131
+ sio = StringIO.new('some gunk')
132
+ expect(sio).not_to be_closed
133
+ ImageVise.close_and_unlink(sio)
134
+ expect(sio).to be_closed
135
+ end
136
+ end
137
+
105
138
  describe 'methods dealing with the operator list' do
106
139
  it 'have the basic operators already set up' do
107
140
  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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-19 00:00:00.000000000 Z
11
+ date: 2018-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: patron
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.6'
19
+ version: '0.9'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.6'
26
+ version: '0.9'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rmagick
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -53,39 +53,59 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: magic_bytes
56
+ name: rack
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '1'
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '3'
62
65
  type: :runtime
63
66
  prerelease: false
64
67
  version_requirements: !ruby/object:Gem::Requirement
65
68
  requirements:
66
- - - "~>"
69
+ - - ">="
67
70
  - !ruby/object:Gem::Version
68
71
  version: '1'
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '3'
69
75
  - !ruby/object:Gem::Dependency
70
- name: rack
76
+ name: format_parser
71
77
  requirement: !ruby/object:Gem::Requirement
72
78
  requirements:
73
79
  - - ">="
74
80
  - !ruby/object:Gem::Version
75
- version: '1'
81
+ version: 0.12.1
76
82
  - - "<"
77
83
  - !ruby/object:Gem::Version
78
- version: '3'
84
+ version: '1.0'
79
85
  type: :runtime
80
86
  prerelease: false
81
87
  version_requirements: !ruby/object:Gem::Requirement
82
88
  requirements:
83
89
  - - ">="
84
90
  - !ruby/object:Gem::Version
85
- version: '1'
91
+ version: 0.12.1
86
92
  - - "<"
87
93
  - !ruby/object:Gem::Version
88
- version: '3'
94
+ version: '1.0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: magic_bytes
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '1'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1'
89
109
  - !ruby/object:Gem::Dependency
90
110
  name: bundler
91
111
  requirement: !ruby/object:Gem::Requirement
@@ -292,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
312
  version: '0'
293
313
  requirements: []
294
314
  rubyforge_project:
295
- rubygems_version: 2.7.3
315
+ rubygems_version: 2.5.2
296
316
  signing_key:
297
317
  specification_version: 4
298
318
  summary: Runtime thumbnailing proxy