rack_grid_thumb 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt ADDED
@@ -0,0 +1,49 @@
1
+ Rack::GridThumb
2
+
3
+ Rack::GridThumb is used to dynamically create thumbnails when in front of rack_grid.
4
+ You should run Rack::GridThumb behind a cache such as Varnish or Rack::Cache
5
+
6
+ Installation
7
+
8
+ # gem install rack_grid_thumb
9
+
10
+ Usage Example with Sinatra.
11
+
12
+ # app.rb
13
+ require 'rack_grid'
14
+ require 'rack_grid_thumb'
15
+
16
+ configure do
17
+ use Rack::GridThumb, :prefix => 'grid'
18
+ use Rack::Grid, :prefix => 'grid'
19
+ end
20
+
21
+ # view.erb
22
+ <img src="/grid/4ba69fde8c8f369a6e000003_50x50.jpg" alt="My Image" />
23
+
24
+ Usage
25
+
26
+ /#{prefix}/#{uid}_50x50.jpg # => Crop and resize to 50x50
27
+ /#{prefix}/#{uid}_50x50-nw.jpg # => Crop and resize with northwest gravity
28
+ /#{prefix}/#{uid}_50x.jpg # => Resize to a width of 50, preserving AR
29
+ /#{prefix}/#{uid}_x50.jpg # => Resize to a height of 50, preserving AR
30
+
31
+
32
+ To prevent pesky end-users and bots from flooding your application with
33
+ render requests you can set up Rack::Thumb to check for an SHA-1 signature
34
+ that is unique to every url. Using this option, only thumbnails requested
35
+ by your templates will be valid. Example:
36
+
37
+ use Rack::Thumb, {
38
+ :secret => "My secret",
39
+ :keylength => "16" # => Only use 16 digits of the SHA-1 key
40
+ }
41
+
42
+ You can then use your +secret+ to generate secure links in your templates:
43
+
44
+ /#{prefix}/#{uid}_50x100-sw-a267c193a7eff046.jpg # => Successful
45
+ /#{prefix}/#{uid}_120x250-a267c193a7eff046.jpg # => Returns a bad request error
46
+
47
+
48
+ Inspired by:
49
+ https://github.com/akdubya/rack-thumb
@@ -0,0 +1,189 @@
1
+ require 'rack'
2
+ require 'mapel'
3
+ require 'digest/sha1'
4
+ require 'tempfile'
5
+
6
+ module Rack
7
+
8
+ class GridThumb
9
+ RE_TH_BASE = /_([0-9]+x|x[0-9]+|[0-9]+x[0-9]+)(-(?:nw|n|ne|w|c|e|sw|s|se))?/
10
+ RE_TH_EXT = /(\.(?:jpg|jpeg|png|gif))/i
11
+ TH_GRAV = {
12
+ '-nw' => :northwest,
13
+ '-n' => :north,
14
+ '-ne' => :northeast,
15
+ '-w' => :west,
16
+ '-c' => :center,
17
+ '-e' => :east,
18
+ '-sw' => :southwest,
19
+ '-s' => :south,
20
+ '-se' => :southeast
21
+ }
22
+
23
+ def initialize(app, options={})
24
+ @app = app
25
+ @keylen = options[:keylength]
26
+ @secret = options[:secret]
27
+ @route = generate_route(options[:prefix])
28
+ end
29
+
30
+ # Generates route given a prefixes.
31
+ def generate_route(prefix = nil)
32
+ if @keylen
33
+ /^(\/#{prefix}\/\w+).*#{RE_TH_BASE}-([0-9a-f]{#{@keylen}})#{RE_TH_EXT}$/
34
+ else
35
+ /^(\/#{prefix}\/\w+).*#{RE_TH_BASE}#{RE_TH_EXT}$/
36
+ end
37
+ end
38
+
39
+ def call(env)
40
+ dup._call(env)
41
+ end
42
+
43
+ def _call(env)
44
+ response = catch(:halt) do
45
+ throw :halt unless %w{GET HEAD}.include? env["REQUEST_METHOD"]
46
+ @env = env
47
+ @path = env["PATH_INFO"]
48
+ if match = @path.match(@route)
49
+ @source, dim, grav = extract_meta(match)
50
+ @image = get_source_image
51
+ @thumb = render_thumbnail(dim, grav) unless head?
52
+ serve
53
+ end
54
+ nil
55
+ end
56
+
57
+ response || @app.call(env)
58
+ end
59
+
60
+ # Extracts filename and options from the path.
61
+ def extract_meta(match)
62
+ result = if @keylen
63
+ extract_signed_meta(match)
64
+ else
65
+ extract_unsigned_meta(match)
66
+ end
67
+
68
+ throw :halt unless result
69
+ result
70
+ end
71
+
72
+ # Extracts filename and options from a signed path.
73
+ def extract_signed_meta(match)
74
+ base, dim, grav, sig, ext = match.captures
75
+ digest = Digest::SHA1.hexdigest("#{base}_#{dim}#{grav}#{ext}#{@secret}")[0..@keylen-1]
76
+ throw(:halt, bad_request) unless sig && (sig == digest)
77
+ [base + ext, dim, grav]
78
+ end
79
+
80
+ # Extracts filename and options from an unsigned path.
81
+ def extract_unsigned_meta(match)
82
+ base, dim, grav, ext = match.captures
83
+ [base + ext, dim, grav]
84
+ end
85
+
86
+ # Fetch the source image from the downstream app, returning the downstream
87
+ # app's response if it is not a success.
88
+ def get_source_image
89
+ status, headers, body = @app.call(@env.merge(
90
+ "PATH_INFO" => @source
91
+ ))
92
+ unless (status >= 200 && status < 300) &&
93
+ (headers["Content-Type"].split("/").first == "image")
94
+ throw :halt, [status, headers, body]
95
+ end
96
+
97
+ @source_headers = headers
98
+
99
+ if !head?
100
+ if body.respond_to?(:path)
101
+ ::File.open(body.path, 'rb')
102
+ elsif body.respond_to?(:each)
103
+ data = ''
104
+ body.each { |part| data << part.to_s }
105
+ Tempfile.new(::File.basename(@path)).tap do |f|
106
+ f.binmode
107
+ f.write(data)
108
+ f.close
109
+ end
110
+ end
111
+ else
112
+ nil
113
+ end
114
+ end
115
+
116
+ # Renders a thumbnail from the source image. Returns a Tempfile.
117
+ def render_thumbnail(dim, grav)
118
+ gravity = grav ? TH_GRAV[grav] : :center
119
+ width, height = parse_dimensions(dim)
120
+ origin_width, origin_height = Mapel.info(@image.path)[:dimensions]
121
+ width = [width, origin_width].min if width
122
+ height = [height, origin_height].min if height
123
+ output = create_tempfile
124
+ cmd = Mapel(@image.path).gravity(gravity)
125
+ if width && height
126
+ cmd.resize!(width, height)
127
+ else
128
+ cmd.resize(width, height, 0, 0, '>')
129
+ end
130
+ cmd.to(output.path).run
131
+ output
132
+ end
133
+
134
+ # Serves the thumbnail. If this is a HEAD request we strip the body as well
135
+ # as the content length because the render was never run.
136
+ def serve
137
+ response = if head?
138
+ @source_headers.delete("Content-Length")
139
+ [200, @source_headers, []]
140
+ else
141
+ [200, @source_headers.merge("Content-Length" => ::File.size(@thumb.path).to_s), self]
142
+ end
143
+
144
+ throw :halt, response
145
+ end
146
+
147
+ # Parses the rendering options; returns false if rendering options are invalid
148
+ def parse_dimensions(meta)
149
+ dimensions = meta.split('x').map do |dim|
150
+ if dim.empty?
151
+ nil
152
+ elsif dim[0].to_i == 0
153
+ throw :halt, bad_request
154
+ else
155
+ dim.to_i
156
+ end
157
+ end
158
+ dimensions.any? ? dimensions : throw(:halt, bad_request)
159
+ end
160
+
161
+ # Creates a new tempfile
162
+ def create_tempfile
163
+ Tempfile.new(::File.basename(@path)).tap { |f| f.close }
164
+ end
165
+
166
+ def bad_request
167
+ body = "Bad thumbnail parameters in #{@path}\n"
168
+ [400, {"Content-Type" => "text/plain",
169
+ "Content-Length" => body.size.to_s},
170
+ [body]]
171
+ end
172
+
173
+ def head?
174
+ @env["REQUEST_METHOD"] == "HEAD"
175
+ end
176
+
177
+ def each
178
+ ::File.open(@thumb.path, "rb") { |file|
179
+ while part = file.read(8192)
180
+ yield part
181
+ end
182
+ }
183
+ end
184
+
185
+ def to_path
186
+ @thumb.path
187
+ end
188
+ end
189
+ end
File without changes
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_grid_thumb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dusty Doris
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-08 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mapel
16
+ requirement: &2157443320 !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: *2157443320
25
+ description: Auto-create thumbnails when used with rack_grid
26
+ email: github@dusty.name
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files:
30
+ - README.txt
31
+ files:
32
+ - README.txt
33
+ - lib/rack_grid_thumb.rb
34
+ - test/test_rack_grid_thumb.rb
35
+ homepage: http://github.com/dusty/rack_grid_thumb
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project: none
55
+ rubygems_version: 1.8.5
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Auto-create thumbnails when used with rack_grid
59
+ test_files: []