scale_down 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +2 -0
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +37 -0
- data/README.rdoc +73 -0
- data/Rakefile +15 -0
- data/color_profiles/sRGB.icm +0 -0
- data/config_sample.ru +8 -0
- data/gen_tags +2 -0
- data/lib/scale_down/controller.rb +29 -0
- data/lib/scale_down/image.rb +72 -0
- data/lib/scale_down/scaler.rb +89 -0
- data/lib/scale_down/version.rb +3 -0
- data/lib/scale_down.rb +12 -0
- data/scale_down.gemspec +31 -0
- data/test/files/cmyk.tif +0 -0
- data/test/files/graphic.png +0 -0
- data/test/files/invalid_jpeg.jpg +0 -0
- data/test/files/orient.jpg +0 -0
- data/test/scale_down/controller_test.rb +45 -0
- data/test/scale_down/image_test.rb +119 -0
- data/test/scale_down/scaler_test.rb +135 -0
- data/test/scale_down_test.rb +50 -0
- data/test/scaled_image_tag_test.rb +23 -0
- data/test/test_helper.rb +21 -0
- metadata +225 -0
data/.bundle/config
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
scale_down (0.0.1)
|
5
|
+
rake (>= 0.8.7)
|
6
|
+
rmagick (>= 2.1)
|
7
|
+
rmagick (>= 2.1)
|
8
|
+
ruby-hmac (>= 0.4.0)
|
9
|
+
sinatra (>= 1.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: http://rubygems.org/
|
13
|
+
specs:
|
14
|
+
contest (0.1.2)
|
15
|
+
mocha (0.9.8)
|
16
|
+
rake
|
17
|
+
rack (1.2.1)
|
18
|
+
rack-test (0.5.6)
|
19
|
+
rack (>= 1.0)
|
20
|
+
rake (0.8.7)
|
21
|
+
rmagick (2.13.1)
|
22
|
+
ruby-hmac (0.4.0)
|
23
|
+
sinatra (1.0)
|
24
|
+
rack (>= 1.0)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
contest (>= 0.1.2)
|
31
|
+
mocha (= 0.9.8)
|
32
|
+
rack-test (= 0.5.6)
|
33
|
+
rake (>= 0.8.7)
|
34
|
+
rmagick (>= 2.1)
|
35
|
+
ruby-hmac (>= 0.4.0)
|
36
|
+
scale_down!
|
37
|
+
sinatra (>= 1.0)
|
data/README.rdoc
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= ScaleDown
|
2
|
+
|
3
|
+
A RESTful server for quickly scaling and serving images. Nothing more.
|
4
|
+
|
5
|
+
Supports cropping images and converts CMYK to RGB.
|
6
|
+
|
7
|
+
Images are scaled based upon their URL. An HMAC signature is used to prevent malicious scaling of images (ie: bring your server down.)
|
8
|
+
|
9
|
+
For example
|
10
|
+
|
11
|
+
[ image server address ][ image source ][ size ][ hmac ]
|
12
|
+
http://images.example.com/images/logo.png/400x300/A3SDACEDF
|
13
|
+
|
14
|
+
would redirect to (via 301) the scaled image
|
15
|
+
|
16
|
+
http://images.example.com/images/scaled/logo-400x300.png
|
17
|
+
|
18
|
+
To crop an image include the 'cropped' option
|
19
|
+
|
20
|
+
http://images.example.com/images/logo.png/400x300-cropped/A3SDACEDF
|
21
|
+
|
22
|
+
Use 'auto' to have an image scale to one dimension or another.
|
23
|
+
|
24
|
+
For example, to ensure an image is 300 pixels wide
|
25
|
+
|
26
|
+
http://images.example.com/images/logo.png/300xauto/A3SDACEDF
|
27
|
+
|
28
|
+
|
29
|
+
== Configuration
|
30
|
+
|
31
|
+
ScaleDown::Scaler.hmac_key = "secret"
|
32
|
+
ScaleDown::Scaler.hmac_method = HMAC::MD5
|
33
|
+
ScaleDown::Scaler.hmac_length = 8
|
34
|
+
ScaleDown::Scaler.root_path = "/tmp/scale_down"
|
35
|
+
|
36
|
+
== Example URL generator
|
37
|
+
|
38
|
+
require 'HMAC::MD5'
|
39
|
+
|
40
|
+
# ie scaled_image_src('/images/picture.png/400x300-cropped')
|
41
|
+
def scaled_image_src(url)
|
42
|
+
hmac = HMAC::MD5.new("secret").update(url).to_s[0...8]
|
43
|
+
"http://images.myserver.com#{url}/#{hmac}"
|
44
|
+
end
|
45
|
+
|
46
|
+
== TODO
|
47
|
+
|
48
|
+
This thing is brand spankin new and is in flux - expect changes.
|
49
|
+
|
50
|
+
Define a max image size (do not scale images over this size).
|
51
|
+
Support DELETE requests to remove scaled images.
|
52
|
+
Custom Read/Write methods. Allow for more than just local storage.
|
53
|
+
|
54
|
+
== Dependencies
|
55
|
+
|
56
|
+
Sinatra
|
57
|
+
RMagick
|
58
|
+
Ruby-HMAC
|
59
|
+
|
60
|
+
== License
|
61
|
+
|
62
|
+
LICENSE:
|
63
|
+
|
64
|
+
(The MIT License)
|
65
|
+
|
66
|
+
Copyright © 2010 John Weir & Fame Driver LLC
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
69
|
+
|
70
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
71
|
+
|
72
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
73
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Test'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
Binary file
|
data/config_sample.ru
ADDED
data/gen_tags
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class ScaleDown::Controller < Sinatra::Application
|
2
|
+
|
3
|
+
set :raise_errors, true
|
4
|
+
set :show_exceptions, false
|
5
|
+
set :static, true
|
6
|
+
set :public, "/Users/johnweir/Sites/famedriver/site/public"
|
7
|
+
|
8
|
+
get '/' do
|
9
|
+
"<b>ScaleDown version #{ScaleDown::VERSION}<b/>"
|
10
|
+
end
|
11
|
+
|
12
|
+
get '/*/:filename/:geometry/:hmac' do
|
13
|
+
path, status = scaler(params)
|
14
|
+
unless status == 403
|
15
|
+
redirect path, status
|
16
|
+
else
|
17
|
+
[status, "Error: this image could not be processed"]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def scaler(params)
|
23
|
+
ScaleDown::Scaler.process \
|
24
|
+
:path => params[:splat].join("/"),
|
25
|
+
:filename => params[:filename],
|
26
|
+
:geometry => params[:geometry],
|
27
|
+
:hmac => params[:hmac]
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
class ScaleDown::Image
|
4
|
+
include Magick
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def scale(properties)
|
8
|
+
new(properties).valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
def geometry(properties)
|
12
|
+
Magick::Geometry.new(properties[:width], properties[:height], nil, nil, ">")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(properties = {})
|
17
|
+
@file = load_file(properties[:file])
|
18
|
+
@out = properties[:out]
|
19
|
+
@geometry = geometry(properties[:options])
|
20
|
+
@options = properties[:options]
|
21
|
+
@wrote = false
|
22
|
+
|
23
|
+
save if @file
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_file(filepath)
|
27
|
+
begin
|
28
|
+
Magick::Image.read(filepath).first
|
29
|
+
rescue Magick::ImageMagickError => e
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def geometry(properties)
|
35
|
+
self.class.geometry properties
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
@wrote
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def save
|
45
|
+
path = Pathname.new(@out).dirname.to_s
|
46
|
+
FileUtils.mkdir_p path unless FileTest.directory? path
|
47
|
+
resize
|
48
|
+
fix_color_space
|
49
|
+
write
|
50
|
+
end
|
51
|
+
|
52
|
+
def fix_color_space
|
53
|
+
if @file.colorspace == Magick::CMYKColorspace
|
54
|
+
@file.add_profile "#{File.expand_path(File.dirname(__FILE__))}/../../color_profiles/sRGB.icm"
|
55
|
+
@file = @file.quantize 2**24, Magick::RGBColorspace
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def resize
|
60
|
+
@file.auto_orient!
|
61
|
+
if @options[:crop]
|
62
|
+
@file.crop_resized!(@geometry.width, @geometry.height, Magick::CenterGravity)
|
63
|
+
else
|
64
|
+
@file.change_geometry!(@geometry) {|cols, rows, img| img.resize!(cols,rows)}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def write
|
69
|
+
@file.write(@out) { self.quality = 85 }
|
70
|
+
@wrote = true
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
require 'hmac-md5'
|
3
|
+
require 'hmac-sha1'
|
4
|
+
|
5
|
+
class ScaleDown::Scaler
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :hmac_method, :hmac_key, :hmac_length
|
9
|
+
attr_accessor :root_path
|
10
|
+
|
11
|
+
def process(params)
|
12
|
+
scaler = new(params)
|
13
|
+
|
14
|
+
return ["Missing file", 404] unless scaler.root_file_exists?
|
15
|
+
return [scaler.redirect_path, 301] if scaler.scaled_file_exists?
|
16
|
+
|
17
|
+
if scaler.valid_hmac? && scaler.scale
|
18
|
+
[scaler.redirect_path, 301]
|
19
|
+
else
|
20
|
+
["Error message", 403]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_hmac?(params)
|
25
|
+
str = [params[:path], "/", params[:filename], "/", params[:geometry]].join
|
26
|
+
hmac(str) == params[:hmac]
|
27
|
+
end
|
28
|
+
|
29
|
+
def hmac(string)
|
30
|
+
hmac_method.new(hmac_key).update(string).to_s[0...hmac_length]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(params)
|
35
|
+
@params = params
|
36
|
+
end
|
37
|
+
|
38
|
+
def image_options
|
39
|
+
dimensions, *options = @params[:geometry].split("-")
|
40
|
+
width, height = dimensions.split("x")
|
41
|
+
{:height => height.to_i, :width => width.to_i}.tap do |o|
|
42
|
+
options.each {|k| o[k.to_sym] = true}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def scale
|
47
|
+
ScaleDown::Image.scale \
|
48
|
+
:file => root_path,
|
49
|
+
:out => scaled_file_path,
|
50
|
+
:options => image_options
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid_hmac?
|
54
|
+
self.class.valid_hmac?(@params)
|
55
|
+
end
|
56
|
+
|
57
|
+
def redirect_path
|
58
|
+
["/"+@params[:path], "scaled", scaled_filename].join("/")
|
59
|
+
end
|
60
|
+
|
61
|
+
def root_file_exists?
|
62
|
+
File.exists? root_path
|
63
|
+
end
|
64
|
+
|
65
|
+
def scaled_file_exists?
|
66
|
+
File.exists? scaled_file_path
|
67
|
+
end
|
68
|
+
|
69
|
+
def root_path
|
70
|
+
File.join(self.class.root_path, @params[:path], @params[:filename])
|
71
|
+
end
|
72
|
+
|
73
|
+
def scaled_file_path
|
74
|
+
File.join(self.class.root_path, redirect_path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def scaled_filename
|
78
|
+
"#{filename}-#{@params[:geometry]}.#{scaled_extension}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def filename
|
82
|
+
@params[:filename].split(".")[0...-1].join(".")
|
83
|
+
end
|
84
|
+
|
85
|
+
def scaled_extension
|
86
|
+
ext = @params[:filename].split(".").last
|
87
|
+
ext == "png" ? ext : "jpg"
|
88
|
+
end
|
89
|
+
end
|
data/lib/scale_down.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
libdir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'sinatra'
|
6
|
+
|
7
|
+
module ScaleDown
|
8
|
+
require 'scale_down/version'
|
9
|
+
require 'scale_down/controller'
|
10
|
+
require 'scale_down/scaler'
|
11
|
+
require 'scale_down/image'
|
12
|
+
end
|
data/scale_down.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "scale_down/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "scale_down"
|
7
|
+
s.version = ScaleDown::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["John Weir"]
|
10
|
+
s.email = ["john@famedriver.com"]
|
11
|
+
s.homepage = "http://github.com/jweir/ScaleDown"
|
12
|
+
s.summary = %q{A Sinatra based server for quickly scaling and serving images. Nothing more.}
|
13
|
+
s.description = %q{}
|
14
|
+
|
15
|
+
s.rubyforge_project = "scale_down"
|
16
|
+
|
17
|
+
s.add_dependency "rmagick", ">= 2.1"
|
18
|
+
s.add_dependency "rake", ">= 0.8.7"
|
19
|
+
s.add_dependency "sinatra", ">= 1.0"
|
20
|
+
s.add_dependency "rmagick", ">= 2.1"
|
21
|
+
s.add_dependency "ruby-hmac", ">= 0.4.0"
|
22
|
+
|
23
|
+
s.add_development_dependency "contest", ">= 0.1.2"
|
24
|
+
s.add_development_dependency "mocha", "0.9.8"
|
25
|
+
s.add_development_dependency "rack-test", "0.5.6"
|
26
|
+
|
27
|
+
s.files = `git ls-files`.split("\n")
|
28
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
29
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
end
|
data/test/files/cmyk.tif
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__))+'/../test_helper'
|
2
|
+
|
3
|
+
class ScaleDown::Controller::Test < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
ScaleDown::Controller
|
8
|
+
end
|
9
|
+
|
10
|
+
context "parsing a request" do
|
11
|
+
|
12
|
+
should "have an image path" do
|
13
|
+
ScaleDown::Scaler.expects(:process).with(
|
14
|
+
:path => "user/path",
|
15
|
+
:filename => "filename.png",
|
16
|
+
:geometry => "400x300-cropped-grayscale",
|
17
|
+
:hmac => "HMAC").
|
18
|
+
returns ["path","status"]
|
19
|
+
|
20
|
+
get '/user/path/filename.png/400x300-cropped-grayscale/HMAC'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "a valid request" do
|
25
|
+
|
26
|
+
should "redirect to the image path" do
|
27
|
+
ScaleDown::Scaler.expects(:process).returns ["/image-path", 301]
|
28
|
+
get "/path/filename/geo/hmac"
|
29
|
+
|
30
|
+
assert_equal 301, last_response.status
|
31
|
+
assert_equal "/image-path", last_response["Location"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "an invalid request" do
|
36
|
+
should "respond with a 403 and error message" do
|
37
|
+
ScaleDown::Scaler.expects(:process).returns ["Error description", 403]
|
38
|
+
|
39
|
+
get "/path/filename/geo/hmac"
|
40
|
+
|
41
|
+
assert_equal 403, last_response.status
|
42
|
+
assert_match "Error", last_response.body
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__))+'/../test_helper'
|
2
|
+
|
3
|
+
class ScaleDown::Image::Test < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def create(file_path, out_path, options)
|
6
|
+
ScaleDown::Image.scale \
|
7
|
+
:file => file_path,
|
8
|
+
:out => out_path,
|
9
|
+
:options => options
|
10
|
+
end
|
11
|
+
|
12
|
+
teardown do
|
13
|
+
FileUtils.rm_r(tests_path("scaled_test")) if File.directory?(tests_path('scaled_test'))
|
14
|
+
end
|
15
|
+
|
16
|
+
context "scaling a valid image" do
|
17
|
+
setup do
|
18
|
+
@subject = create \
|
19
|
+
tests_path("files/graphic.png"),
|
20
|
+
tests_path("scaled_test/graphic_scaled.png"),
|
21
|
+
{ :width => 100, :height => 180}
|
22
|
+
end
|
23
|
+
|
24
|
+
should "save the file (and generate the path)" do
|
25
|
+
assert File.exists?(tests_path('scaled_test/graphic_scaled.png'))
|
26
|
+
end
|
27
|
+
|
28
|
+
should "scale the image" do
|
29
|
+
image = Magick::Image.read(tests_path("scaled_test/graphic_scaled.png")).first
|
30
|
+
assert_equal 90, image.columns
|
31
|
+
assert_equal 180, image.rows
|
32
|
+
end
|
33
|
+
|
34
|
+
should "be true" do
|
35
|
+
assert @subject
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "#geometry" do
|
40
|
+
should "convert 'auto' to nil" do
|
41
|
+
geo = ScaleDown::Image.geometry(:width => "auto", :height => "400")
|
42
|
+
assert_equal 0.0, geo.width
|
43
|
+
assert_equal 400, geo.height
|
44
|
+
end
|
45
|
+
|
46
|
+
should "auto scale" do
|
47
|
+
assert create \
|
48
|
+
tests_path("files/graphic.png"),
|
49
|
+
tests_path("scaled_test/graphic_scaled.png"),
|
50
|
+
{ :width => "auto", :height => 150 }
|
51
|
+
|
52
|
+
image = Magick::Image.read(tests_path("scaled_test/graphic_scaled.png")).first
|
53
|
+
assert_equal 150, image.rows
|
54
|
+
assert_equal 75, image.columns
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "an invalid file" do
|
59
|
+
setup do
|
60
|
+
@subject = create \
|
61
|
+
tests_path("files/invalid_jpeg.jpg"),
|
62
|
+
tests_path("scaled_test/graphic_scaled.jpg"),
|
63
|
+
{ :width => 100, :height => 105 }
|
64
|
+
end
|
65
|
+
|
66
|
+
should "return nil" do
|
67
|
+
assert !@subject
|
68
|
+
end
|
69
|
+
|
70
|
+
should "not create a scaled image" do
|
71
|
+
assert !File.exists?(tests_path("scaled_test/graphic_scaled.jpg"))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "cropping" do
|
76
|
+
setup do
|
77
|
+
@subject = create \
|
78
|
+
tests_path("files/graphic.png"),
|
79
|
+
tests_path("scaled_test/graphic_scaled.png"),
|
80
|
+
{ :width => 25, :height => 25, :crop => true }
|
81
|
+
end
|
82
|
+
|
83
|
+
should "crop the image to the dimensions" do
|
84
|
+
image = Magick::Image.read(tests_path("scaled_test/graphic_scaled.png")).first
|
85
|
+
assert_equal 25, image.columns
|
86
|
+
assert_equal 25, image.rows
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "orientation" do
|
91
|
+
setup do
|
92
|
+
@subject = create \
|
93
|
+
tests_path("files/orient.jpg"),
|
94
|
+
tests_path("scaled_test/graphic_scaled.jpg"),
|
95
|
+
{ :width => "auto", :height => 800}
|
96
|
+
end
|
97
|
+
|
98
|
+
should "be automatic" do
|
99
|
+
image = Magick::Image.read(tests_path("scaled_test/graphic_scaled.jpg")).first
|
100
|
+
assert_equal 600, image.columns
|
101
|
+
assert_equal 800, image.rows
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "color correction" do
|
106
|
+
setup do
|
107
|
+
@subject = create \
|
108
|
+
tests_path("files/cmyk.tif"),
|
109
|
+
tests_path("scaled_test/graphic_scaled.jpg"),
|
110
|
+
{ :width => "auto", :height => 200}
|
111
|
+
end
|
112
|
+
|
113
|
+
should "be automatic" do
|
114
|
+
image = Magick::Image.read(tests_path("scaled_test/graphic_scaled.jpg")).first
|
115
|
+
assert_equal Magick::RGBColorspace, image.colorspace
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__))+'/../test_helper'
|
2
|
+
|
3
|
+
class ScaleDown::Scaler::Test < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Scaler::Test" do
|
6
|
+
setup do
|
7
|
+
ScaleDown::Scaler.hmac_key = "secret"
|
8
|
+
ScaleDown::Scaler.hmac_method = HMAC::MD5
|
9
|
+
ScaleDown::Scaler.hmac_length = 8
|
10
|
+
ScaleDown::Scaler.root_path = "/tmp"
|
11
|
+
|
12
|
+
hmac = HMAC::MD5.new("secret").update("file/path/filename.png/400x300-crop").to_s
|
13
|
+
|
14
|
+
@params = {
|
15
|
+
:path => "file/path",
|
16
|
+
:filename => "filename.png",
|
17
|
+
:geometry => "400x300-crop",
|
18
|
+
:hmac => hmac[0...8]
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
context "HMAC" do
|
23
|
+
should "validate when the params match the HMAC signature" do
|
24
|
+
assert ScaleDown::Scaler.valid_hmac?(@params)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "not validate when the params do not match the HMAC signature" do
|
28
|
+
assert !ScaleDown::Scaler.valid_hmac?(@params.merge(:path => "file/different"))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "instance" do
|
33
|
+
setup do
|
34
|
+
@scaler = ScaleDown::Scaler.new @params
|
35
|
+
end
|
36
|
+
|
37
|
+
should "validate the HMAC" do
|
38
|
+
ScaleDown::Scaler.expects(:valid_hmac?).with(@params).returns true
|
39
|
+
assert @scaler.valid_hmac?
|
40
|
+
end
|
41
|
+
|
42
|
+
should "determine root file existance" do
|
43
|
+
File.expects(:exists?).with("/tmp/file/path/filename.png").returns true
|
44
|
+
assert @scaler.root_file_exists?
|
45
|
+
end
|
46
|
+
|
47
|
+
should "deterimine the scaled image's existance" do
|
48
|
+
File.expects(:exists?).with("/tmp/file/path/scaled/filename-400x300-crop.png").returns true
|
49
|
+
assert @scaler.scaled_file_exists?
|
50
|
+
end
|
51
|
+
|
52
|
+
should "parse the geometry params into options" do
|
53
|
+
assert_equal({:width => 400, :height => 300, :crop => true}, @scaler.image_options)
|
54
|
+
end
|
55
|
+
|
56
|
+
should "have a redirect path" do
|
57
|
+
assert_equal "/file/path/scaled/filename-400x300-crop.png", @scaler.redirect_path
|
58
|
+
end
|
59
|
+
|
60
|
+
should "process the image" do
|
61
|
+
ScaleDown::Image.expects(:scale).with(
|
62
|
+
:file => @scaler.root_path,
|
63
|
+
:out => @scaler.scaled_file_path,
|
64
|
+
:options => @scaler.image_options).returns true
|
65
|
+
|
66
|
+
assert @scaler.scale
|
67
|
+
end
|
68
|
+
|
69
|
+
should "default to a jpg out file" do
|
70
|
+
["jpg", "tga", "tif", "pdf", "psd"].each do |ext|
|
71
|
+
scaler = ScaleDown::Scaler.new @params.merge(:filename => "test.#{ext}")
|
72
|
+
assert_match /\.jpg$/, scaler.scaled_file_path
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
should "use a png for png graphics" do
|
77
|
+
scaler = ScaleDown::Scaler.new @params.merge(:filename => "test.png")
|
78
|
+
assert_match /\.png$/, scaler.scaled_file_path
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "process response" do
|
83
|
+
|
84
|
+
context "for an existing, unscaled image" do
|
85
|
+
setup do
|
86
|
+
File.expects(:exists?).with("/tmp/file/path/filename.png").returns true
|
87
|
+
File.expects(:exists?).with("/tmp/file/path/scaled/filename-400x300-crop.png").returns false
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with a valid HMAC" do
|
91
|
+
setup do
|
92
|
+
ScaleDown::Scaler.expects(:valid_hmac?).returns true
|
93
|
+
ScaleDown::Image.expects(:scale).returns true
|
94
|
+
end
|
95
|
+
|
96
|
+
should "scale the image" do
|
97
|
+
ScaleDown::Scaler.process(@params)
|
98
|
+
end
|
99
|
+
|
100
|
+
should "return a 301 redirect to the processed image's URL" do
|
101
|
+
assert_equal ["/file/path/scaled/filename-400x300-crop.png", 301], ScaleDown::Scaler.process(@params)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "without a valid HMAC" do
|
106
|
+
should "return a 403 Forbidden response" do
|
107
|
+
ScaleDown::Scaler.expects(:valid_hmac?).returns false
|
108
|
+
assert_equal 403, ScaleDown::Scaler.process(@params)[1]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "for an existing, scaled, image" do
|
114
|
+
setup do
|
115
|
+
File.expects(:exists?).with("/tmp/file/path/filename.png").returns true
|
116
|
+
File.expects(:exists?).with("/tmp/file/path/scaled/filename-400x300-crop.png").returns true
|
117
|
+
end
|
118
|
+
|
119
|
+
should "return a 301 redirect to the processed image's URL" do
|
120
|
+
assert_equal ["/file/path/scaled/filename-400x300-crop.png", 301], ScaleDown::Scaler.process(@params)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "for a missing image" do
|
125
|
+
setup do
|
126
|
+
File.expects(:exists?).with("/tmp/file/path/filename.png").returns false
|
127
|
+
end
|
128
|
+
|
129
|
+
should "return a 404" do
|
130
|
+
assert_equal 404, ScaleDown::Scaler.process(@params)[1]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__))+'/test_helper'
|
2
|
+
|
3
|
+
class ScaleDown::Test < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
ScaleDown::Controller
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid_get(path)
|
11
|
+
get "/#{path}/#{hmac(path, "secret")[0...8]}"
|
12
|
+
end
|
13
|
+
|
14
|
+
context "integration test" do
|
15
|
+
setup do
|
16
|
+
ScaleDown::Scaler.hmac_key = "secret"
|
17
|
+
ScaleDown::Scaler.hmac_method = HMAC::MD5
|
18
|
+
ScaleDown::Scaler.hmac_length = 8
|
19
|
+
ScaleDown::Scaler.root_path = "/tmp/scale_down"
|
20
|
+
|
21
|
+
FileUtils.mkdir_p("/tmp/scale_down/test_images/example_1")
|
22
|
+
FileUtils.cp tests_path("files/graphic.png"), "/tmp/scale_down/test_images/example_1/graphic.png"
|
23
|
+
FileUtils.mkdir_p("/tmp/scale_down/test_images/example_2")
|
24
|
+
FileUtils.cp tests_path("files/invalid_jpeg.jpg"), "/tmp/scale_down/test_images/example_2/invalid_jpeg.jpg"
|
25
|
+
end
|
26
|
+
|
27
|
+
teardown do
|
28
|
+
FileUtils.rm_r("/tmp/scale_down")
|
29
|
+
end
|
30
|
+
|
31
|
+
should "get an image and scale it" do
|
32
|
+
valid_get 'test_images/example_1/graphic.png/400x300-cropped'
|
33
|
+
assert_equal 301, last_response.status
|
34
|
+
assert_equal "/test_images/example_1/scaled/graphic-400x300-cropped.png", last_response["Location"]
|
35
|
+
assert File.exists?("/tmp/scale_down/test_images/example_1/scaled/graphic-400x300-cropped.png")
|
36
|
+
end
|
37
|
+
|
38
|
+
should "get a nonexistant image and return a 404" do
|
39
|
+
valid_get "test_exmaples/example_none/image.jpg"
|
40
|
+
assert_equal 404, last_response.status
|
41
|
+
end
|
42
|
+
|
43
|
+
should "get an invalid image and return a 403" do
|
44
|
+
valid_get 'test_images/example_2/invalid_jpeg.jpg/400x300-cropped'
|
45
|
+
|
46
|
+
assert_equal 403, last_response.status
|
47
|
+
assert !File.exists?("/tmp/scale_down/test_images/example_2/scaled/invalid_jpeg-400x300-cropped.jpg")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__))+'/test_helper'
|
2
|
+
|
3
|
+
class ScaleDown::Test < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def scaled_image_src(url)
|
6
|
+
hmac = HMAC::MD5.new("secret").update(url).to_s[0...8]
|
7
|
+
"http://images.myserver.com#{url}/#{hmac}"
|
8
|
+
end
|
9
|
+
|
10
|
+
setup do
|
11
|
+
ScaleDown::Scaler.hmac_key = "secret"
|
12
|
+
ScaleDown::Scaler.hmac_method = HMAC::MD5
|
13
|
+
ScaleDown::Scaler.hmac_length = 8
|
14
|
+
ScaleDown::Scaler.root_path = "/tmp"
|
15
|
+
end
|
16
|
+
|
17
|
+
should "create a URL with the HMAC signature" do
|
18
|
+
hmac = ScaleDown::Scaler.hmac("/images/graphic.png/400x300-cropped")
|
19
|
+
assert_equal "http://images.myserver.com/images/graphic.png/400x300-cropped/#{hmac}", scaled_image_src("/images/graphic.png/400x300-cropped")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'lib/scale_down'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'contest'
|
4
|
+
require 'mocha'
|
5
|
+
require 'ruby-debug'
|
6
|
+
|
7
|
+
ENV['RACK_ENV'] = 'test'
|
8
|
+
|
9
|
+
require 'forwardable'
|
10
|
+
|
11
|
+
class Test::Unit::TestCase
|
12
|
+
|
13
|
+
def hmac(path, secret)
|
14
|
+
hmac = HMAC::MD5.new(secret).update(path).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def tests_path(append)
|
18
|
+
File.join(File.expand_path(File.dirname(__FILE__)), append)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scale_down
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- John Weir
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-19 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rmagick
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 1
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 1
|
33
|
+
version: "2.1"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 49
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 8
|
48
|
+
- 7
|
49
|
+
version: 0.8.7
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: sinatra
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 15
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 0
|
64
|
+
version: "1.0"
|
65
|
+
type: :runtime
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rmagick
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 1
|
76
|
+
segments:
|
77
|
+
- 2
|
78
|
+
- 1
|
79
|
+
version: "2.1"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: ruby-hmac
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 15
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
- 4
|
94
|
+
- 0
|
95
|
+
version: 0.4.0
|
96
|
+
type: :runtime
|
97
|
+
version_requirements: *id005
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: contest
|
100
|
+
prerelease: false
|
101
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 31
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
- 1
|
110
|
+
- 2
|
111
|
+
version: 0.1.2
|
112
|
+
type: :development
|
113
|
+
version_requirements: *id006
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: mocha
|
116
|
+
prerelease: false
|
117
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - "="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 43
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
- 9
|
126
|
+
- 8
|
127
|
+
version: 0.9.8
|
128
|
+
type: :development
|
129
|
+
version_requirements: *id007
|
130
|
+
- !ruby/object:Gem::Dependency
|
131
|
+
name: rack-test
|
132
|
+
prerelease: false
|
133
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - "="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
hash: 7
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
- 5
|
142
|
+
- 6
|
143
|
+
version: 0.5.6
|
144
|
+
type: :development
|
145
|
+
version_requirements: *id008
|
146
|
+
description: ""
|
147
|
+
email:
|
148
|
+
- john@famedriver.com
|
149
|
+
executables: []
|
150
|
+
|
151
|
+
extensions: []
|
152
|
+
|
153
|
+
extra_rdoc_files: []
|
154
|
+
|
155
|
+
files:
|
156
|
+
- .bundle/config
|
157
|
+
- .gitignore
|
158
|
+
- Gemfile
|
159
|
+
- Gemfile.lock
|
160
|
+
- README.rdoc
|
161
|
+
- Rakefile
|
162
|
+
- color_profiles/sRGB.icm
|
163
|
+
- config_sample.ru
|
164
|
+
- gen_tags
|
165
|
+
- lib/scale_down.rb
|
166
|
+
- lib/scale_down/controller.rb
|
167
|
+
- lib/scale_down/image.rb
|
168
|
+
- lib/scale_down/scaler.rb
|
169
|
+
- lib/scale_down/version.rb
|
170
|
+
- scale_down.gemspec
|
171
|
+
- test/files/cmyk.tif
|
172
|
+
- test/files/graphic.png
|
173
|
+
- test/files/invalid_jpeg.jpg
|
174
|
+
- test/files/orient.jpg
|
175
|
+
- test/scale_down/controller_test.rb
|
176
|
+
- test/scale_down/image_test.rb
|
177
|
+
- test/scale_down/scaler_test.rb
|
178
|
+
- test/scale_down_test.rb
|
179
|
+
- test/scaled_image_tag_test.rb
|
180
|
+
- test/test_helper.rb
|
181
|
+
has_rdoc: true
|
182
|
+
homepage: http://github.com/jweir/ScaleDown
|
183
|
+
licenses: []
|
184
|
+
|
185
|
+
post_install_message:
|
186
|
+
rdoc_options: []
|
187
|
+
|
188
|
+
require_paths:
|
189
|
+
- lib
|
190
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
191
|
+
none: false
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
hash: 3
|
196
|
+
segments:
|
197
|
+
- 0
|
198
|
+
version: "0"
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
none: false
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
hash: 3
|
205
|
+
segments:
|
206
|
+
- 0
|
207
|
+
version: "0"
|
208
|
+
requirements: []
|
209
|
+
|
210
|
+
rubyforge_project: scale_down
|
211
|
+
rubygems_version: 1.3.7
|
212
|
+
signing_key:
|
213
|
+
specification_version: 3
|
214
|
+
summary: A Sinatra based server for quickly scaling and serving images. Nothing more.
|
215
|
+
test_files:
|
216
|
+
- test/files/cmyk.tif
|
217
|
+
- test/files/graphic.png
|
218
|
+
- test/files/invalid_jpeg.jpg
|
219
|
+
- test/files/orient.jpg
|
220
|
+
- test/scale_down/controller_test.rb
|
221
|
+
- test/scale_down/image_test.rb
|
222
|
+
- test/scale_down/scaler_test.rb
|
223
|
+
- test/scale_down_test.rb
|
224
|
+
- test/scaled_image_tag_test.rb
|
225
|
+
- test/test_helper.rb
|