image_vise 0.0.28 → 0.1.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
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