image_vise 0.0.28 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71d0c2785fe1650fef5a5023dca5d3ead9bdab97
4
- data.tar.gz: 4758619a643d835eb9622259bd6b09d362148496
3
+ metadata.gz: e9dbe4f26897009011c3a9f42b233e7e232d6634
4
+ data.tar.gz: 6910d9c75d0a2c2c13b9a3688d7302cd22758992
5
5
  SHA512:
6
- metadata.gz: 1e17b34585c22cc8bed8e3a6d0868b5970c2527fcec99af67469da8acf78e474e6c09b99db57fad64c0bb98439662cf90076382a2fb65bc1226c5019ed4c6594
7
- data.tar.gz: 409c7e9d30630df9784389a807da35d38296e614821399dd64c0d4bc138b53eb93c6ea3160ddf0674e7580078eac0f778b2cb943a47a5f8f25c94b7d14297c4c
6
+ metadata.gz: 1bb4e6b545fc8d847ed9a6462c4cf5c2bc3b4cec035df034e0c5cc7da982d9cc8bc680fc438c467ea5e6e3b063a2eb26c31daf250bcb59324899488e7a14f58d
7
+ data.tar.gz: 01d4a26bf68dead0b3c58d5c6ecbf07759be957a8c5594e57ca3b9e792d92dbd061ad7ee46cf7301f113cc730eea473d87e8c626ec05b7dc4c12590858fc62b2
data/README.md CHANGED
@@ -6,7 +6,8 @@ framework. The main uses are:
6
6
  * Image resizing on request
7
7
  * Applying image filters
8
8
 
9
- It is implemented as a Rack application that responds to any URL and accepts the following query string parameters:
9
+ It is implemented as a Rack application that responds to any URL and accepts the following two _last_ path
10
+ compnents, internally named `q` and `sig`:
10
11
 
11
12
  * `q` - Base64 encoded JSON object with `src_url` and `pipeline` properties
12
13
  (the source URL of the image and processing steps to apply)
@@ -14,7 +15,7 @@ It is implemented as a Rack application that responds to any URL and accepts the
14
15
 
15
16
  A request to `ImageVise` might look like this:
16
17
 
17
- /?q=acbhGyfhyYErghff&sig=acfgheg123
18
+ /acbhGyfhyYErghff/acfgheg123
18
19
 
19
20
  The URL that gets generated is best composed with the included `ImageVise.image_params` method. This method will
20
21
  take care of encoding the source URL and the commands in the right way, as well as signing.
@@ -38,11 +39,11 @@ You might want to define a helper method for generating signed URLs as well, whi
38
39
 
39
40
  ```ruby
40
41
  def thumb_url(source_image_url)
41
- qs_params = ImageVise.image_params(src_url: source_image_url, secret: ENV.fetch('IMAGE_VISE_SECRET')) do |pipeline|
42
+ path = ImageVise.image_path(src_url: source_image_url, secret: ENV.fetch('IMAGE_VISE_SECRET')) do |pipeline|
42
43
  # For example, you can also yield `pipeline` to the caller
43
44
  pipeline.fit_crop width: 128, height: 128, gravity: 'c'
44
45
  end
45
- '/images?' + Rack::Utils.build_query(qs_params) # or use url_for...
46
+ '/images' + path
46
47
  end
47
48
  ```
48
49
 
@@ -65,13 +66,13 @@ You might want to define a helper method for generating signed URLs as well, whi
65
66
 
66
67
  ```ruby
67
68
  def thumb_url(source_image_url)
68
- qs_params = ImageVise.image_params(src_url: source_image_url, secret: ENV.fetch('IMAGE_VISE_SECRET')) do |pipe|
69
+ path_param = ImageVise.image_path(src_url: source_image_url, secret: ENV.fetch('IMAGE_VISE_SECRET')) do |pipe|
69
70
  pipe.fit_crop width: 256, height: 256, gravity: 'c'
70
71
  pipe.sharpen sigma: 0.5, radius: 2
71
72
  pipe.ellipse_stencil
72
73
  end
73
74
  # Output a URL to the app
74
- '/images?' + Rack::Utils.build_query(image_request)
75
+ '/images' + path
75
76
  end
76
77
  ```
77
78
 
data/image_vise.gemspec CHANGED
@@ -2,11 +2,11 @@
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.28 ruby lib
5
+ # stub: image_vise 0.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "image_vise"
9
- s.version = "0.0.28"
9
+ s.version = "0.1.0"
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"]
@@ -29,6 +29,10 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
29
29
  raise InvalidRequest.new(e.message)
30
30
  end
31
31
 
32
+ def to_path_params(signed_with_secret)
33
+ '/%{q}/%{sig}' % to_query_string_params(signed_with_secret)
34
+ end
35
+
32
36
  def to_query_string_params(signed_with_secret)
33
37
  payload = JSON.dump(to_h)
34
38
  base64_enc = Base64.strict_encode64(payload).gsub(/\=+$/, '')
@@ -67,11 +67,9 @@ class ImageVise::RenderEngine
67
67
 
68
68
  req = parse_env_into_request(env)
69
69
  bail(405, 'Only GET supported') unless req.get?
70
+ params = extract_params_from_request(req)
70
71
 
71
- # Prevent cache bypass DOS attacks by only permitting :sig and :q
72
- bail(400, 'Too many params') if req.params.length > 2
73
-
74
- image_request = ImageVise::ImageRequest.from_params(qs_params: req.params, secrets: ImageVise.secret_keys)
72
+ image_request = ImageVise::ImageRequest.from_params(qs_params: params, secrets: ImageVise.secret_keys)
75
73
  render_destination_file, render_file_type, etag = process_image_request(image_request)
76
74
  image_rack_response(render_destination_file, render_file_type, etag)
77
75
  rescue *permanent_failures => e
@@ -98,6 +96,24 @@ class ImageVise::RenderEngine
98
96
  Rack::Request.new(rack_env)
99
97
  end
100
98
 
99
+ # Extracts the image params from the Rack::Request
100
+ #
101
+ # @param rack_request[#path_info] an object that has a path info
102
+ # @return [Hash] the params hash with `:q` and `:sig` keys
103
+ def extract_params_from_request(rack_request)
104
+ # Prevent cache bypass DOS attacks by only permitting :sig and :q
105
+ bail(400, 'Query strings are not supported') if rack_request.params.any?
106
+
107
+ # Extract the last two path components
108
+ *, q_from_path, sig_from_path = rack_request.path_info.split('/')
109
+
110
+ # Raise if any of them are empty or blank
111
+ nothing_recovered = [q_from_path, sig_from_path].all?{|v| v.nil? || v.empty? }
112
+ bail(400, 'Need 2 usable path components') if nothing_recovered
113
+
114
+ {q: q_from_path, sig: sig_from_path}
115
+ end
116
+
101
117
  # Processes the ImageRequest object created from the request parameters,
102
118
  # and returns a triplet of the File object containing the rendered image,
103
119
  # the MagicBytes::FileType object of the render, and the cache ETag value
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.28'
11
+ VERSION = '0.1.0'
12
12
  S_MUTEX = Mutex.new
13
13
  private_constant :S_MUTEX
14
14
 
@@ -84,6 +84,25 @@ class ImageVise
84
84
  ImageRequest.new(src_url: URI(src_url), pipeline: p).to_query_string_params(secret)
85
85
  end
86
86
 
87
+ # Generate a path for a resized image. Yields a Pipeline object that
88
+ # will receive method calls for adding image operations to a stack.
89
+ #
90
+ # ImageVise.image_path(src_url: image_url_on_s3, secret: '...') do |p|
91
+ # p.center_fit width: 128, height: 128
92
+ # p.elliptic_stencil
93
+ # end #=> "/abcdef/xyz123"
94
+ #
95
+ # The query string elements can be then passed on to RenderEngine for validation and execution.
96
+ #
97
+ # @yield {ImageVise::Pipeline}
98
+ # @return [String]
99
+ def image_path(src_url:, secret:)
100
+ p = Pipeline.new
101
+ yield(p)
102
+ raise ArgumentError, "Image pipeline has no steps defined" if p.empty?
103
+ ImageRequest.new(src_url: URI(src_url), pipeline: p).to_path_params(secret)
104
+ end
105
+
87
106
  # Adds an operator
88
107
  def add_operator(operator_name, object_responding_to_new)
89
108
  @operators[operator_name.to_s] = object_responding_to_new
@@ -26,6 +26,14 @@ describe ImageVise::ImageRequest do
26
26
  expect(image_request.src_url).to be_kind_of(URI)
27
27
  end
28
28
 
29
+ it 'composes path parameters' do
30
+ parametrized = double(to_params: {foo: 'bar'})
31
+ uri = URI('http://example.com/image.psd')
32
+ image_request = described_class.new(src_url: uri, pipeline: parametrized)
33
+ path = image_request.to_path_params('password')
34
+ expect(path).to start_with('/eyJwaXB')
35
+ expect(path).to end_with('f207b')
36
+ end
29
37
 
30
38
  it 'never apppends "="-padding to the Base64-encoded "q"' do
31
39
  parametrized = double(to_params: {foo: 'bar'})
@@ -21,10 +21,9 @@ describe ImageVise::RenderEngine do
21
21
 
22
22
  p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
23
23
  image_request = ImageVise::ImageRequest.new(src_url: 'http://unknown.com/image.jpg', pipeline: p)
24
- params = image_request.to_query_string_params('l33tness')
25
24
  expect(app).to receive(:handle_generic_error).and_call_original
26
25
  expect {
27
- get '/', params
26
+ get image_request.to_path_params('l33tness')
28
27
  }.to raise_error(/No keys set/)
29
28
  end
30
29
  end
@@ -48,7 +47,6 @@ describe ImageVise::RenderEngine do
48
47
 
49
48
  p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
50
49
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
51
- params = image_request.to_query_string_params('l33tness')
52
50
 
53
51
  expect_any_instance_of(Patron::Session).to receive(:get_file) {|_self, url, path|
54
52
  File.open(path, 'wb') {|f| f << 'totally not an image' }
@@ -56,7 +54,7 @@ describe ImageVise::RenderEngine do
56
54
  }
57
55
  expect(app).to receive(:handle_request_error).and_call_original
58
56
 
59
- get '/', params
57
+ get image_request.to_path_params('l33tness')
60
58
  expect(last_response.status).to eq(422)
61
59
  expect(last_response['Cache-Control']).to eq("private, max-age=0, no-cache")
62
60
  expect(last_response.body).to include('Unsupported/unknown')
@@ -69,9 +67,8 @@ describe ImageVise::RenderEngine do
69
67
 
70
68
  p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
71
69
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
72
- params = image_request.to_query_string_params('l33tness')
73
70
 
74
- get '/', params
71
+ get image_request.to_path_params('l33tness')
75
72
  expect(last_response.status).to eq(403)
76
73
  expect(last_response.body).to include('filesystem access is disabled')
77
74
  end
@@ -84,9 +81,8 @@ describe ImageVise::RenderEngine do
84
81
 
85
82
  p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
86
83
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
87
- params = image_request.to_query_string_params('l33tness')
88
84
 
89
- get '/', params
85
+ get image_request.to_path_params('l33tness')
90
86
  expect(last_response.status).to eq(403)
91
87
  expect(last_response.headers['Content-Type']).to eq('application/json')
92
88
  parsed = JSON.load(last_response.body)
@@ -104,9 +100,9 @@ describe ImageVise::RenderEngine do
104
100
 
105
101
  p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
106
102
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
107
- params = image_request.to_query_string_params('l33tness')
108
103
 
109
- get '/', params
104
+ get image_request.to_path_params('l33tness')
105
+
110
106
  expect(last_response.status).to eq(error_code)
111
107
  expect(last_response.headers).to have_key('Cache-Control')
112
108
  expect(last_response.headers['Cache-Control']).to eq("private, max-age=0, no-cache")
@@ -124,20 +120,20 @@ describe ImageVise::RenderEngine do
124
120
 
125
121
  p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 35, gravity: 'c')
126
122
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
127
- params = image_request.to_query_string_params('l33tness')
128
123
 
129
- get '/', params
124
+ req_path = image_request.to_path_params('l33tness')
130
125
 
126
+ get req_path, {}
131
127
  expect(last_response).to be_ok
132
128
  expect(last_response['ETag']).not_to be_nil
133
129
  expect(last_response['Cache-Control']).to match(/public/)
134
130
 
135
- get '/', params, {'HTTP_IF_NONE_MATCH' => last_response['ETag']}
131
+ get req_path, {}, {'HTTP_IF_NONE_MATCH' => last_response['ETag']}
136
132
  expect(last_response.status).to eq(304)
137
133
 
138
134
  # Should consider _any_ ETag a request to rerender something
139
135
  # that already exists in an upstream cache
140
- get '/', params, {'HTTP_IF_NONE_MATCH' => SecureRandom.hex(4)}
136
+ get req_path, {}, {'HTTP_IF_NONE_MATCH' => SecureRandom.hex(4)}
141
137
  expect(last_response.status).to eq(304)
142
138
  end
143
139
 
@@ -148,9 +144,8 @@ describe ImageVise::RenderEngine do
148
144
 
149
145
  p = ImageVise::Pipeline.new.geom(geometry_string: '512x335').fit_crop(width: 10, height: 10, gravity: 'c')
150
146
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
151
- params = image_request.to_query_string_params('l33tness')
152
147
 
153
- get '/', params
148
+ get image_request.to_path_params('l33tness')
154
149
  expect(last_response.status).to eq(200)
155
150
 
156
151
  expect(last_response.headers['Content-Type']).to eq('image/jpeg')
@@ -175,7 +170,7 @@ describe ImageVise::RenderEngine do
175
170
  expect(app).to receive(:output_file_type_permitted?).and_call_original
176
171
  expect(app).to receive(:enable_forking?).and_call_original
177
172
 
178
- get '/', params
173
+ get image_request.to_path_params('l33tness')
179
174
  expect(last_response.status).to eq(200)
180
175
  end
181
176
 
@@ -186,9 +181,8 @@ describe ImageVise::RenderEngine do
186
181
 
187
182
  p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
188
183
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
189
- params = image_request.to_query_string_params('l33tness')
190
184
 
191
- get '/', params
185
+ get image_request.to_path_params('l33tness')
192
186
  expect(last_response.status).to eq(200)
193
187
  expect(last_response.headers['Content-Type']).to eq('image/jpeg')
194
188
  end
@@ -205,9 +199,8 @@ describe ImageVise::RenderEngine do
205
199
 
206
200
  p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
207
201
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
208
- params = image_request.to_query_string_params('l33tness')
209
202
 
210
- get '/', params
203
+ get image_request.to_path_params('l33tness')
211
204
  File.unlink(utf8_file_path)
212
205
  expect(last_response.status).to eq(200)
213
206
  expect(last_response.headers['Content-Type']).to eq('image/jpeg')
@@ -218,10 +211,8 @@ describe ImageVise::RenderEngine do
218
211
 
219
212
  p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
220
213
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
221
- params = image_request.to_query_string_params('l33tness')
222
214
 
223
- params[:extra] = '123'
224
- get '/', params
215
+ get image_request.to_path_params('l33tness'), {'extra' => '123'}
225
216
 
226
217
  expect(last_response.status).to eq(400)
227
218
  end
@@ -233,9 +224,8 @@ describe ImageVise::RenderEngine do
233
224
 
234
225
  p = ImageVise::Pipeline.new.geom(geometry_string: '220x220').ellipse_stencil
235
226
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
236
- params = image_request.to_query_string_params('l33tness')
237
227
 
238
- get '/', params
228
+ get image_request.to_path_params('l33tness')
239
229
  expect(last_response.status).to eq(200)
240
230
 
241
231
  expect(last_response.headers['Content-Type']).to eq('image/png')
@@ -251,9 +241,8 @@ describe ImageVise::RenderEngine do
251
241
 
252
242
  p = ImageVise::Pipeline.new.geom(geometry_string: '220x220').ellipse_stencil
253
243
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
254
- params = image_request.to_query_string_params('l33tness')
255
244
 
256
- get '/', params
245
+ get image_request.to_path_params('l33tness')
257
246
  expect(last_response.status).to eq(422)
258
247
  expect(last_response.body).to include('unknown input file format .psd')
259
248
  end
@@ -265,13 +254,12 @@ describe ImageVise::RenderEngine do
265
254
 
266
255
  p = ImageVise::Pipeline.new.geom(geometry_string: '220x220')
267
256
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
268
- params = image_request.to_query_string_params('l33tness')
269
257
 
270
258
  class << app
271
259
  def source_file_type_permitted?(type); true; end
272
260
  end
273
261
 
274
- get '/', params
262
+ get image_request.to_path_params('l33tness')
275
263
  expect(last_response.status).to eq(200)
276
264
  expect(last_response.headers['Content-Type']).to eq('image/png')
277
265
  end
@@ -283,13 +271,12 @@ describe ImageVise::RenderEngine do
283
271
 
284
272
  p = ImageVise::Pipeline.new.geom(geometry_string: '220x220')
285
273
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
286
- params = image_request.to_query_string_params('l33tness')
287
274
 
288
275
  class << app
289
276
  def source_file_type_permitted?(type); true; end
290
277
  end
291
278
 
292
- get '/', params
279
+ get image_request.to_path_params('l33tness')
293
280
  expect(last_response.status).to eq(200)
294
281
  expect(last_response.headers['Content-Type']).to eq('image/png')
295
282
  end
@@ -301,14 +288,14 @@ describe ImageVise::RenderEngine do
301
288
 
302
289
  p = ImageVise::Pipeline.new.geom(geometry_string: '220x220')
303
290
  image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
304
- params = image_request.to_query_string_params('l33tness')
291
+
305
292
 
306
293
  class << app
307
294
  def source_file_type_permitted?(type); true; end
308
295
  def output_file_type_permitted?(type); true; end
309
296
  end
310
297
 
311
- get '/', params
298
+ get image_request.to_path_params('l33tness')
312
299
  expect(last_response.status).to eq(200)
313
300
  expect(last_response.headers['Content-Type']).to eq('image/tiff')
314
301
  end
@@ -77,6 +77,15 @@ describe ImageVise do
77
77
  end
78
78
  end
79
79
 
80
+ describe '.image_path' do
81
+ it 'returns the path to the image within the application' do
82
+ path = ImageVise.image_path(src_url: 'file://tmp/img.jpg', secret: 'a') do |p|
83
+ p.ellipse_stencil
84
+ end
85
+ expect(path).to start_with('/')
86
+ end
87
+ end
88
+
80
89
  describe 'methods dealing with the operator list' do
81
90
  it 'have the basic operators already set up' do
82
91
  oplist = ImageVise.defined_operator_names
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_vise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.28
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov