rack-thumb-proxy 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-thumb-proxy.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'ruby-debug19'
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Lee Hambley
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Rack::Thumb::Proxy
2
+
3
+ Resize remotely hosted images not hosted on your servers dynamically.
4
+ Safely proxy request
5
+
6
+ `Rack::Thumb::Proxy` is a project in the spirit of `Rack::Thumb`, but
7
+ for use in situations when one doesn't host the images statically on
8
+ one's own server, consider such an example:
9
+
10
+ <img src="http://a-site-which-doesnt-run-ssl.com/the/product/image.png" />
11
+
12
+ One could download, resize, host and be responsible for this image, but
13
+ in the days of realtime systems, massive data, clustered storage,
14
+ etcetera, why bother? One could hot-link the image, but then this
15
+ doesn't work for cross-protocol, with https in the mix.
16
+
17
+ <img src="/media/thumbs/http%3A%2F%2Fa-site-which-doesnt-run-ssl.com%2Fthe%2Fproduct%2Fimage.png" />
18
+
19
+ Where, in this case one has predefined `thumbs` as a category. One could
20
+ also do something such as:
21
+
22
+ <img src="/media/50x50/http%3A%2F%2Fa-site-which-doesnt-run-ssl.com%2Fthe%2Fproduct%2Fimage.png" />
23
+
24
+ or even
25
+
26
+ <img src="/media/50x50/http%3A%2F%2Fa-site-which-doesnt-run-ssl.com%2Fthe%2Fproduct%2Fimage.png" />
27
+
28
+ When combined with a CDN or Rack::Cache, this shouldn't cause too heavy
29
+ a performance penalty, and images from upstream will even be cached
30
+ locally.
31
+
32
+ ## Safety
33
+
34
+ To ensure that someone doesn't decide to use your server resources to resize
35
+ their entire collection of cat pictures, there's a hash mechanism which
36
+ is also available. This works very much like `Rack::Thumb`.
37
+
38
+ To use this feature, simply configure `Rack::Thumb::Proxy` with a
39
+ `secret`, and a `key_length` (the latter may be ommitted and defaults
40
+ to 10), urls will be then generated with the following appearance:
41
+
42
+ <img src="/media/15a5683b74/50x50/http%3A%2F%2Fa-site-which-doesnt-run-ssl.com%2Fthe%2Fproduct%2Fimage.png" />
43
+
44
+ The key is calculated as a result of the following pattern:
45
+
46
+ "%s\t%s\t%s" % <secret>, <options>, <url encoded image source>
47
+
48
+ For example with a terrible secret of `secret`:
49
+
50
+ echo "secret\t50x50\thttp%3A%2F%2Fa-site-which-doesnt-run-ssl.com%2Fthe%2Fproduct%2Fimage.png" | openssl dgst -sha1
51
+
52
+ The resulting SHA is as you see above. it is recommended that you choose
53
+ a secret using a token generation tool, if you are using Rails, you have
54
+ one baked-in simply use `rake secret` from your Rails project root.
55
+
56
+ Requests which do not match the expected format will receive a `400 Bad
57
+ Request` response.
58
+
59
+ ## (Rails) Helpers
60
+
61
+ A helper module is provided which can be used in Rails, Sinatra, or your
62
+ unit tests. This is loaded automatically via a Railtie into Rails,
63
+ available from all views. The following methods are defined:
64
+
65
+ proxied_image_url("image url", options)
66
+ proxied_image_tag("image url", options)
67
+
68
+ Somewhat of a private API are the following, which you may find useful:
69
+
70
+ signature_hash_for("image url", options)
71
+
72
+ The image url passed here should not be URL encoded, as
73
+ `Rack::Thumb::Proxy` will encode it correctly for you.
74
+
75
+ ## `options`
76
+
77
+ "50x" `[String]`Constrain to 50 pixels height, maintaining
78
+ original aspect ratio
79
+
80
+ "x100" `[String]`Constrain to 100 pixels width, maintaining
81
+ original aspect ratio
82
+
83
+ "50x75n" `[String]` Constrain to 50 pixels height, distorting the
84
+ image to acheive a 75 pixels width with *northern* gravity
85
+ (see below)
86
+
87
+ "50x75" `[String]` Crop to 50 pixels height, without distorting the
88
+ image to acheive a 75 pixels width
89
+
90
+ :label `[Symbol]` Take the options specified in
91
+ the label (see below)
92
+
93
+ {width: 123, height: } `[Hash]` The keys `width`, `height`, and
94
+ `gravity` are accepted
95
+
96
+ ## Option Labels
97
+
98
+ One can use the configuration API as such to name a label:
99
+
100
+ Rack::Thumb::Proxy.configure do
101
+ option_label :product_thumbnail, "100x100"
102
+ end
103
+
104
+ ## Gravity
105
+
106
+ Gravity can be specified which will pull the crop (in the case that both
107
+ width, and height are given), it will focus the cropped area of the
108
+ image, valid options are `n`, `ne`, `e`, `se`, `s`, `sw`, `w`, `nw`. The
109
+ default gracity is `c`, which will focus the crop on the centre of the
110
+ image.
111
+
112
+ ## No Magic
113
+
114
+ If you don't need to resize the image, specifying a magical option of
115
+ `noop` disables any kind of resizing, this is useful if you just need to
116
+ use the software a as a proxy. When operating in this mode there is no
117
+ dependency on imagemagick.
118
+
119
+ ## Installation
120
+
121
+ Add this line to your application's Gemfile:
122
+
123
+ gem 'rack-thumb-proxy'
124
+
125
+ And then execute:
126
+
127
+ $ bundle
128
+
129
+ Or install it yourself as:
130
+
131
+ $ gem install rack-thumb-proxy
132
+
133
+ ## Usage
134
+
135
+ The included railtie will ensure that this is available in your Rails
136
+ application, you can simply use something like:
137
+
138
+ match '/media', :to => Rack::Thumb::Proxy
139
+
140
+ If you need to configure additional options, this can be done in an
141
+ initializer, or by passing a configuaation hash to the
142
+ Rack::Thumb::Proxy initialzer. The former is preferred.
143
+
144
+ ## Example Configuration
145
+
146
+ Rack::Thumb::Proxy.configure do
147
+ prefix "/media/"
148
+ secret "d94bba3d2e0b4809a570158506"
149
+ key_length 10
150
+ end
151
+
152
+ When one doesn't want to use the configuration API, the more succinct
153
+ version would be to do something like:
154
+
155
+ # ./config/routes.rb
156
+ match '/media' => Rack::Thumb::Proxy { prefix: "/",
157
+ secret: "ABC1234", key_length: 10 }
158
+
159
+ # config.ru
160
+ use Rack::Thumb::Proxy { prefix: "/", secret: "ABC1234", key_length: 10 }
161
+
162
+ One complication is that when using the link generator functions, one
163
+ **must** use the configuration API, otherwise the default path will be
164
+ `/`.
165
+
166
+ ## To Do
167
+
168
+ * Implement Railtie/helpers.
169
+ * Ensure the hash signatures are checked.
170
+ * Make it possible to control the cache control header.
171
+ * Don't use open-uri.
172
+ * Check earlier in the process that upstream is an image,
173
+ don't rely on MiniMagick to blow up on non-image content.
174
+ * Take the cache-control headers from upstream
175
+ if we can.
176
+ * Allow a local cache for the images, perhaps somewhere
177
+ in `/tmp`.
178
+ * Actually support option labels, it just looks good in
179
+ the readme right now, alas.
180
+
181
+ ## Contributing
182
+
183
+ 1. Fork it
184
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
185
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
186
+ 4. Push to the branch (`git push origin my-new-feature`)
187
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList['test/test*.rb']
9
+ end
10
+
11
+ task :default => [:test]
data/example/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rack-thumb-proxy'
5
+
6
+ run Rack::Thumb::Proxy
@@ -0,0 +1,53 @@
1
+ module Rack
2
+ module Thumb
3
+ class Proxy
4
+
5
+ class Configuration
6
+
7
+ attr_reader :cache_control_headers
8
+ attr_reader :option_labels
9
+
10
+ def initialize
11
+ initialize_defaults!
12
+ end
13
+
14
+ def mount_point(new_mount_point = nil)
15
+ @mount_point = new_mount_point if new_mount_point
16
+ return @mount_point
17
+ end
18
+
19
+ def key_length(new_key_length = nil)
20
+ @key_length = new_key_length if new_key_length
21
+ return @key_length
22
+ end
23
+
24
+ def secret(new_secret = nil)
25
+ @secret = new_secret if new_secret
26
+ return @secret
27
+ end
28
+
29
+ def option_label(label, options)
30
+ option_labels.merge!(label.to_sym => options)
31
+ end
32
+
33
+ def hash_signatures_in_use?
34
+ !!@secret
35
+ end
36
+
37
+ def initialize_defaults!
38
+ @secret = nil
39
+ @key_length = 10
40
+ @mount_point = '/'
41
+ @option_labels = {}
42
+ @cache_control_headers = {'Cache-Control' => 'max-age=86400, public, must-revalidate'}
43
+ end
44
+ alias :reset_defaults! :initialize_defaults!
45
+ private :initialize_defaults!
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,11 @@
1
+ module Rack
2
+ module Thumb
3
+ class Proxy
4
+ class Railtie < Rails::Railtie
5
+ initializer "rack-thumb-proxy.view_helpers" do
6
+ ActionView::Base.send :include, ViewHelpers
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Rack
2
+ module Thumb
3
+ class Proxy
4
+ VERSION = "0.0.8"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,55 @@
1
+ module Rack
2
+ module Thumb
3
+ class Proxy
4
+ module ViewHelpers
5
+
6
+ def proxied_image_url(image_url, options = nil)
7
+
8
+ if rack_thumb_proxy_hash_signatures_enabled?
9
+ signature = hash_signature_for(image_url, options)
10
+ end
11
+
12
+ options = rack_thumb_proxy_options_to_s(options)
13
+ escaped_image_url = escape_image_url(image_url)
14
+ mount_point = rack_thumb_proxy_configuration.mount_point
15
+
16
+ mount_point + [signature, options, escaped_image_url].compact.join("/")
17
+
18
+ end
19
+
20
+ def hash_signature_for(image_url, options = nil)
21
+ return nil unless rack_thumb_proxy_hash_signatures_enabled?
22
+ key_length = rack_thumb_proxy_configuration.key_length
23
+ secret = rack_thumb_proxy_configuration.secret
24
+ ('%s\t%s\t%s' % [secret, options, escape_image_url(image_url)])[0..key_length-1]
25
+ end
26
+
27
+ private
28
+
29
+ def escape_image_url(image_url)
30
+ CGI.escape(image_url)
31
+ end
32
+
33
+ def rack_thumb_proxy_options_to_s(options = nil)
34
+ return nil if options.nil?
35
+ if options.is_a?(String)
36
+ options
37
+ elsif options.is_a?(Hash)
38
+ sprintf("%sx%s%s", options[:width], options[:height], options[:gravity])
39
+ else
40
+ raise RuntimeError, 'Not implemented yet, see the TODO in README.md'
41
+ end
42
+ end
43
+
44
+ def rack_thumb_proxy_configuration
45
+ Rack::Thumb::Proxy.configuration
46
+ end
47
+
48
+ def rack_thumb_proxy_hash_signatures_enabled?
49
+ rack_thumb_proxy_configuration.hash_signatures_in_use?
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,240 @@
1
+ require 'cgi'
2
+ require 'open-uri'
3
+ require 'tempfile'
4
+ require 'rack-thumb-proxy/version'
5
+ require 'rack-thumb-proxy/configuration'
6
+ require 'rack-thumb-proxy/view_helpers'
7
+
8
+ require 'rack-thumb-proxy/railtie' if defined?(Rails)
9
+
10
+ module Rack
11
+
12
+ module Thumb
13
+
14
+ class Proxy
15
+
16
+ class << self
17
+
18
+ attr_writer :configuration
19
+
20
+ def configure(&block)
21
+ configuration.instance_eval(&block)
22
+ configuration
23
+ end
24
+
25
+ def configuration
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ def call(env)
30
+ new(env).call
31
+ end
32
+
33
+ end
34
+
35
+ def initialize(env)
36
+ @env = env
37
+ @path = env['PATH_INFO']
38
+ end
39
+
40
+ def call
41
+ if request_matches?
42
+ validate_signature! &&
43
+ retreive_upstream! &&
44
+ transform_image! &&
45
+ format_response!
46
+ response.finish
47
+ else
48
+ [404, {'Content-Length' => 9.to_s, 'Content-Type' => 'text/plain'}, ['Not Found']]
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def validate_signature!
55
+ true
56
+ end
57
+
58
+ def retreive_upstream!
59
+ begin
60
+ open(request_url, 'rb') do |f|
61
+ tempfile.binmode
62
+ tempfile.write(f.read)
63
+ tempfile.flush
64
+ end
65
+ rescue
66
+ write_error_to_response!
67
+ return false
68
+ end
69
+ return true
70
+ end
71
+
72
+ def format_response!
73
+ response.status = 200
74
+ response.headers["Content-Type"] = mime_type_from_request_url
75
+ response.headers["Content-Length"] = transformed_image_file_size_in_bytes.to_s
76
+ response.body << read_tempfile
77
+ true
78
+ end
79
+
80
+ def read_tempfile
81
+ tempfile.rewind
82
+ tempfile.read
83
+ end
84
+
85
+ def tempfile
86
+ @_tempfile ||= Tempfile.new('rack_thumb_proxy')
87
+ end
88
+
89
+ def tempfile_path
90
+ tempfile.path
91
+ end
92
+
93
+ def transform_image!
94
+
95
+ return true unless should_resize?
96
+
97
+ begin
98
+ require 'mapel'
99
+
100
+ width, height = dimensions_from_request_options
101
+ owidth, oheight = dimensions_from_tempfile
102
+
103
+ width = [width, owidth].min if width
104
+ height = [height, oheight].min if height
105
+
106
+ cmd = Mapel(tempfile_path)
107
+
108
+ if width && height
109
+ cmd.gravity(request_gravity)
110
+ cmd.resize!(width, height)
111
+ else
112
+ cmd.resize(width, height, 0, 0, '>')
113
+ end
114
+
115
+ cmd.to(tempfile_path).run
116
+
117
+ rescue
118
+ puts $!, $@
119
+ write_error_to_response!
120
+ return false
121
+ end
122
+
123
+ true
124
+ end
125
+
126
+ def should_resize?
127
+ !request_options.empty?
128
+ end
129
+
130
+ def should_verify_hash_signature?
131
+ configuration.hash_signatures_in_use?
132
+ end
133
+
134
+ def configuration
135
+ self.class.configuration
136
+ end
137
+
138
+ def request_hash_signature
139
+ @_request_match_data["hash_signature"]
140
+ end
141
+
142
+ def request_options
143
+ @_request_match_data["options"]
144
+ end
145
+
146
+ def request_gravity
147
+ {
148
+ 'nw' => :northwest,
149
+ 'n' => :north,
150
+ 'ne' => :northeast,
151
+ 'w' => :west,
152
+ 'c' => :center,
153
+ 'e' => :east,
154
+ 'sw' => :southwest,
155
+ 's' => :south,
156
+ 'se' => :southeast
157
+ }.fetch(request_gravity_shorthand, :center)
158
+ end
159
+
160
+ def request_gravity_shorthand
161
+ @_request_match_data["gravity"]
162
+ end
163
+
164
+ def request_url
165
+ CGI.unescape(escaped_request_url)
166
+ end
167
+
168
+ def escaped_request_url
169
+ @_request_match_data["escaped_url"]
170
+ end
171
+
172
+ def request_matches?
173
+ @_request_match_data = @path.match(routing_pattern)
174
+ end
175
+
176
+ def witdh_from_tempfile
177
+ dimensions_from_tempfile.first
178
+ end
179
+
180
+ def height_from_tempfile
181
+ dimensions_from_tempfile.last
182
+ end
183
+
184
+ def dimensions_from_tempfile
185
+ require 'mapel' unless defined?(Mapel)
186
+ Mapel.info(tempfile_path)[:dimensions]
187
+ end
188
+
189
+ def width_from_request_options
190
+ dimensions_from_request_options.first
191
+ end
192
+
193
+ def height_from_request_options
194
+ dimensions_from_request_options.last
195
+ end
196
+
197
+ def dimensions_from_request_options
198
+ width, height = request_options.split('x').map(&:to_i).collect { |dim| dim == 0 ? nil : dim }
199
+ [width, height]
200
+ end
201
+
202
+ def transformed_image_file_size_in_bytes
203
+ ::File.size(tempfile_path)
204
+ end
205
+
206
+ # Examples: http://rubular.com/r/oPRK1t31yv
207
+ def routing_pattern
208
+ /^\/(?<hash_signature>[a-z0-9]{10}|)\/?(?<options>(:?[0-9]*x+[0-9]*|))(?<gravity>c|n|ne|e|s|sw|w|nw|)\/?(?<escaped_url>https?.*)$/
209
+ end
210
+
211
+ def response
212
+ @_response ||= Rack::Response.new
213
+ end
214
+
215
+ def write_error_to_response!
216
+ response.status = 500
217
+ response.headers['Content-Type'] = 'text/plain'
218
+ response.body << $!.message
219
+ response.body << "\n\n"
220
+ response.body << $!.backtrace.join("\n")
221
+ end
222
+
223
+ def request_url_file_extension
224
+ ::File.extname(request_url)
225
+ end
226
+
227
+ def mime_type_from_request_url
228
+ {
229
+ '.png' => 'image/png',
230
+ '.gif' => 'image/gif',
231
+ '.jpg' => 'image/jpeg',
232
+ '.jpeg' => 'image/jpeg'
233
+ }.fetch(request_url_file_extension, 'application/octet-stream')
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+
240
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rack-thumb-proxy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+
6
+ gem.authors = ["Lee Hambley"]
7
+ gem.email = ["lee.hambley@gmail.com"]
8
+ gem.description = %q{ Rack middleware for resizing proxied requests for images which don't reside on your own servers. }
9
+ gem.summary = %q{ For more information see https://github.com/leehambley/rack-thumb-proxy }
10
+ gem.homepage = ""
11
+
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ gem.files = `git ls-files`.split("\n")
14
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ gem.name = "rack-thumb-proxy"
16
+ gem.require_paths = ["lib"]
17
+
18
+ gem.version = Rack::Thumb::Proxy::VERSION
19
+
20
+ gem.add_dependency 'rack'
21
+
22
+ gem.add_development_dependency 'minitest', '~> 2.11'
23
+ gem.add_development_dependency 'webmock', '~> 1.8.0'
24
+ gem.add_development_dependency 'rack-test', '~> 0.6.1'
25
+ gem.add_development_dependency 'mapel'
26
+ gem.add_development_dependency 'dimensions'
27
+
28
+ end
Binary file
Binary file
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+
3
+ class TestConfigurationApi < MiniTest::Unit::TestCase
4
+
5
+ def test_it_should_return_a_configuration_object
6
+ assert Rack::Thumb::Proxy.configuration.is_a?(Rack::Thumb::Proxy::Configuration)
7
+ end
8
+
9
+ def test_default_options
10
+ Rack::Thumb::Proxy.configuration.reset_defaults!
11
+ default_configuration = Rack::Thumb::Proxy.configuration
12
+ assert_equal nil, default_configuration.secret
13
+ assert_equal 10, default_configuration.key_length
14
+ assert_equal '/', default_configuration.mount_point
15
+ assert_equal({'Cache-Control' => 'max-age=86400, public, must-revalidate'}, default_configuration.cache_control_headers)
16
+ end
17
+
18
+ def test_it_should_take_a_block_which_is_class_evaluated_keeping_all_options
19
+ Rack::Thumb::Proxy.configuration.reset_defaults!
20
+ configuration = Rack::Thumb::Proxy.configure do
21
+ secret '123'
22
+ key_length 10
23
+ mount_point '/'
24
+ end
25
+ assert_equal '123', configuration.secret
26
+ end
27
+
28
+ def test_setting_option_labels_via_the_configuration_api
29
+ Rack::Thumb::Proxy.configuration.reset_defaults!
30
+ configuration = Rack::Thumb::Proxy.configure do
31
+ option_label :thumbnail, '50x75c'
32
+ end
33
+ assert configuration.option_labels.has_key?(:thumbnail)
34
+ end
35
+
36
+ def test_hash_signatures_are_correctly_enabled_and_disabled_based_on_the_presense_of_the_secret
37
+ Rack::Thumb::Proxy.configuration.reset_defaults!
38
+ configuration = Rack::Thumb::Proxy.configuration
39
+ refute configuration.secret
40
+ refute configuration.hash_signatures_in_use?
41
+ configuration.secret('something truthy')
42
+ assert configuration.secret
43
+ assert configuration.hash_signatures_in_use?
44
+ end
45
+
46
+ end
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'cgi'
5
+
6
+ require 'rack-thumb-proxy'
7
+
8
+ require 'rack/test'
9
+
10
+ require 'dimensions'
11
+
12
+ require 'digest/md5'
13
+
14
+ require 'minitest/autorun'
15
+ require 'minitest/pride'
16
+ require 'minitest/mock'
17
+
18
+ require 'webmock/minitest'
19
+
20
+ WebMock.disable_net_connect!
21
+
22
+ require 'ruby-debug'
23
+ require 'mapel'
24
+
25
+ class ViewHelperSurrogate
26
+ include Rack::Thumb::Proxy::ViewHelpers
27
+ end
@@ -0,0 +1,124 @@
1
+ require 'test_helper'
2
+
3
+ class TestRackThumbProxy < MiniTest::Unit::TestCase
4
+
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ Rack::Thumb::Proxy
9
+ end
10
+
11
+ def test_it_should_repond_with_not_found_when_the_upstream_url_is_bunk
12
+ get "/something/that/doesnt/even/match/a/little/bit"
13
+ assert last_response.not_found?
14
+ end
15
+
16
+ def test_it_should_return_success_with_the_correct_content_length_when_a_matching_url_is_found_for_a_noop_image
17
+ stub_image_request!('250x.gif', 'http://www.example.com/images/noop-kittens.gif')
18
+ get '/' + escape('http://www.example.com/images/noop-kittens.gif')
19
+ assert last_response.ok?
20
+ assert_equal file_size_for_fixture('250x.gif').to_s, last_response.headers.fetch('Content-Length')
21
+ assert_equal file_hash_for_fixture('250x.gif'), file_hash_from_string(last_response.body)
22
+ assert_dimensions last_response.body, 250, 250
23
+ end
24
+
25
+ def test_it_should_return_a_smaller_image_when_resizing_with_minimagick
26
+ stub_image_request!('250x.gif', 'http://www.example.com/images/kittens.gif')
27
+ get '/50x50/' + escape('http://www.example.com/images/kittens.gif')
28
+ assert last_response.ok?
29
+ assert_dimensions last_response.body, 50, 50
30
+ end
31
+
32
+ def test_it_should_crop_when_the_ratio_cannot_be_maintained
33
+ stub_image_request!('200x100.gif', 'http://www.example.com/images/sharkjumping.gif')
34
+ get '/50x50/' + escape('http://www.example.com/images/sharkjumping.gif')
35
+ assert last_response.ok?
36
+ assert_dimensions last_response.body, 50, 50
37
+ end
38
+
39
+ def test_it_should_retain_the_aspec_ratio
40
+ stub_image_request!('200x100.gif', 'http://www.example.com/images/sharkjumping.gif')
41
+ get '/50x/' + escape('http://www.example.com/images/sharkjumping.gif')
42
+ assert last_response.ok?
43
+ assert_dimensions last_response.body, 50, 25
44
+ end
45
+
46
+ def test_it_should_accept_the_gravity_setting_without_breaking_the_resize
47
+ stub_image_request!('200x100.gif', 'http://www.example.com/images/sharkjumping.gif')
48
+ get '/50x50e/' + escape('http://www.example.com/images/sharkjumping.gif')
49
+ assert last_response.ok?
50
+ east = last_response.body
51
+ get '/50x50w/' + escape('http://www.example.com/images/sharkjumping.gif')
52
+ assert last_response.ok?
53
+ west = last_response.body
54
+ refute_equal file_hash_from_string(east), file_hash_from_string(west)
55
+ end
56
+
57
+ def test_default_gravity_is_center_when_cropping
58
+ stub_image_request!('200x100.gif', 'http://www.example.com/images/sharkjumping.gif')
59
+ get '/75x75/' + escape('http://www.example.com/images/sharkjumping.gif')
60
+ no_gravity = last_response.body
61
+ get '/75x75c/' + escape('http://www.example.com/images/sharkjumping.gif')
62
+ c_gravity = last_response.body
63
+ assert_equal file_hash_from_string(no_gravity), file_hash_from_string(c_gravity)
64
+ end
65
+
66
+ def test_should_respond_with_the_proper_mimetype_for_known_image_types
67
+ skip
68
+ end
69
+
70
+ def test_should_respond_with_application_octet_stream_mimetype_for_unknown
71
+ skip
72
+ end
73
+
74
+ private
75
+
76
+ def stub_image_request!(file, url)
77
+ stub_request(:any, url).to_return(
78
+ body: File.read(filename_for_fixture(file)),
79
+ headers: {
80
+ 'Content-Length' => file_size_for_fixture(file)
81
+ },
82
+ status: 200
83
+ )
84
+ end
85
+
86
+ def escape(string)
87
+ CGI.escape(string)
88
+ end
89
+
90
+ def file_size_for_fixture(file)
91
+ File.size(filename_for_fixture(file))
92
+ end
93
+
94
+ def file_hash_from_string(string)
95
+ Digest::MD5.hexdigest(string)
96
+ end
97
+
98
+ def file_hash_for_fixture(file)
99
+ file_hash_from_string(File.read(filename_for_fixture(file)))
100
+ end
101
+
102
+ def filename_for_fixture(file)
103
+ File.expand_path('test/fixtures/' + file)
104
+ end
105
+
106
+ def fixture_file_exists?(file)
107
+ File.exists?(file) rescue false
108
+ end
109
+
110
+ def assert_dimensions(input, expected_width, expected_height)
111
+ unless fixture_file_exists?(input)
112
+ tf = Tempfile.new('assert_dimensions')
113
+ tf.write input
114
+ tf.flush
115
+ tf.rewind
116
+ input = tf.path
117
+ end
118
+ actual_width, actual_height = Dimensions.dimensions(input)
119
+ assert_equal "#{expected_width}x#{expected_height}", "#{actual_width}x#{actual_height}"
120
+ ensure
121
+ tf.close if tf rescue nil
122
+ end
123
+
124
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class TestViewHelpers < MiniTest::Unit::TestCase
4
+
5
+ def test_without_any_options_or_special_configuration_the_view_helper_encodes_the_image_url_correctly
6
+ Rack::Thumb::Proxy.configuration.reset_defaults!
7
+ assert_equal "/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png")
8
+ end
9
+
10
+ def test_without_any_options_with_a_mount_point_configured_the_view_helper_encodes_the_image_url_correctly
11
+ Rack::Thumb::Proxy.configuration.reset_defaults!
12
+ Rack::Thumb::Proxy.configuration.mount_point("/mount/")
13
+ assert_equal "/mount/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png")
14
+ end
15
+
16
+ def test_with_a_width_option_only_configured_the_view_helper_encodes_the_image_url_correctly
17
+ Rack::Thumb::Proxy.configuration.reset_defaults!
18
+ assert_equal "/50x/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png", '50x')
19
+ end
20
+
21
+ def test_with_a_height_option_only_configured_the_view_helper_encodes_the_image_url_correctly
22
+ Rack::Thumb::Proxy.configuration.reset_defaults!
23
+ assert_equal "/x50/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png", 'x50')
24
+ end
25
+
26
+ def test_with_a_width_and_height_option_configured_the_view_helper_encodes_the_image_url_correctly
27
+ Rack::Thumb::Proxy.configuration.reset_defaults!
28
+ assert_equal "/75x50/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png", '75x50')
29
+ end
30
+
31
+ def test_with_a_width_and_height_option_as_a_hash_configured_the_view_helper_encodes_the_image_url_correctly
32
+ Rack::Thumb::Proxy.configuration.reset_defaults!
33
+ assert_equal "/45x90/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png", {width: 45, height: 90})
34
+ end
35
+
36
+ def test_with_a_width_height_and_gravity_options_as_a_hash_configured_the_view_helper_encodes_the_image_url_correctly
37
+ Rack::Thumb::Proxy.configuration.reset_defaults!
38
+ assert_equal "/45x90s/http%3A%2F%2Fwww.example.com%2F1.png", vh.proxied_image_url("http://www.example.com/1.png", {width: 45, height: 90, gravity: :s})
39
+ end
40
+
41
+ private
42
+
43
+ def vh
44
+ ViewHelperSurrogate.new
45
+ end
46
+
47
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-thumb-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lee Hambley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: &70327349437160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70327349437160
25
+ - !ruby/object:Gem::Dependency
26
+ name: minitest
27
+ requirement: &70327349436340 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.11'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70327349436340
36
+ - !ruby/object:Gem::Dependency
37
+ name: webmock
38
+ requirement: &70327349435680 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70327349435680
47
+ - !ruby/object:Gem::Dependency
48
+ name: rack-test
49
+ requirement: &70327349435200 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.6.1
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70327349435200
58
+ - !ruby/object:Gem::Dependency
59
+ name: mapel
60
+ requirement: &70327349434720 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70327349434720
69
+ - !ruby/object:Gem::Dependency
70
+ name: dimensions
71
+ requirement: &70327349434020 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70327349434020
80
+ description: ! ' Rack middleware for resizing proxied requests for images which don''t
81
+ reside on your own servers. '
82
+ email:
83
+ - lee.hambley@gmail.com
84
+ executables: []
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - .gitignore
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - example/config.ru
94
+ - lib/rack-thumb-proxy.rb
95
+ - lib/rack-thumb-proxy/configuration.rb
96
+ - lib/rack-thumb-proxy/railtie.rb
97
+ - lib/rack-thumb-proxy/version.rb
98
+ - lib/rack-thumb-proxy/view_helpers.rb
99
+ - rack-thumb-proxy.gemspec
100
+ - test/fixtures/200x100.gif
101
+ - test/fixtures/250x.gif
102
+ - test/test_configuration_api.rb
103
+ - test/test_helper.rb
104
+ - test/test_rack_thumb_proxy.rb
105
+ - test/test_view_helpers.rb
106
+ homepage: ''
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.10
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: For more information see https://github.com/leehambley/rack-thumb-proxy
130
+ test_files:
131
+ - test/fixtures/200x100.gif
132
+ - test/fixtures/250x.gif
133
+ - test/test_configuration_api.rb
134
+ - test/test_helper.rb
135
+ - test/test_rack_thumb_proxy.rb
136
+ - test/test_view_helpers.rb