imageproxy 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.
- data/.document +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +21 -0
- data/README.mdown +296 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/config.ru +7 -0
- data/imageproxy.gemspec +90 -0
- data/imageproxy.rb +4 -0
- data/lib/command.rb +22 -0
- data/lib/compare.rb +12 -0
- data/lib/convert.rb +72 -0
- data/lib/identify.rb +11 -0
- data/lib/options.rb +73 -0
- data/lib/selftest.rb +72 -0
- data/lib/server.rb +105 -0
- data/lib/signature.rb +24 -0
- data/public/background.png +0 -0
- data/public/sample.png +0 -0
- data/public/sample_10x20.png +0 -0
- data/spec/command_spec.rb +19 -0
- data/spec/convert_spec.rb +151 -0
- data/spec/options_spec.rb +87 -0
- data/spec/server_spec.rb +122 -0
- data/spec/signature_spec.rb +74 -0
- data/spec/spec_helper.rb +23 -0
- metadata +184 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require "#{File.dirname(__FILE__)}/../imageproxy"
|
3
|
+
require "#{File.dirname(__FILE__)}/../lib/options"
|
4
|
+
|
5
|
+
describe Options do
|
6
|
+
describe "parsing path" do
|
7
|
+
context "a simple URL" do
|
8
|
+
subject { Options.new "/process/color/blue/size/medium", {} }
|
9
|
+
its(:command) { should == "process" }
|
10
|
+
its(:color) { should == "blue" }
|
11
|
+
its(:size) { should == "medium" }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "source" do
|
15
|
+
context "when double-escaped" do
|
16
|
+
subject { Options.new "/process/source/http%253A%252F%252Fexample.com%252Fdog.jpg", {} }
|
17
|
+
it("should unescape") { subject.source.should == "http://example.com/dog.jpg" }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when escaped" do
|
21
|
+
subject { Options.new "/process/source/http%3A%2F%2Fexample.com%2Fdog.jpg", {} }
|
22
|
+
it("should unescape") { subject.source.should == "http://example.com/dog.jpg" }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when not escaped" do
|
26
|
+
subject { Options.new "/process/source/foo", {} }
|
27
|
+
it("should not unescape") { subject.source.should == "foo" }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when parameter is named 'src'" do
|
31
|
+
subject { Options.new "/process/src/foo", {} }
|
32
|
+
it("should rename to 'source'") { subject.source.should == "foo" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "signature" do
|
37
|
+
context "when escaped with + signs" do
|
38
|
+
subject { Options.new "/process/source/foo/signature/foo+bar", {} }
|
39
|
+
it("should keep the + sign") { subject.signature.should == "foo+bar" }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "adding query params" do
|
45
|
+
subject { Options.new "/convert/source/foo", { "resize" => "20x20" } }
|
46
|
+
it("should add query params") { subject.resize.should == "20x20" }
|
47
|
+
it("should keep params from path") { subject.source.should == "foo" }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "content type" do
|
51
|
+
context "when guessing based on source filename" do
|
52
|
+
it("should understand .png") { Options.new("/convert", "source" => "foo.png").content_type.should == "image/png" }
|
53
|
+
it("should understand .jpg") { Options.new("/convert", "source" => "foo.jpg").content_type.should == "image/jpeg" }
|
54
|
+
it("should understand .JPEG") { Options.new("/convert", "source" => "foo.JPEG").content_type.should == "image/jpeg" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "obfuscation" do
|
59
|
+
it "should allow the query string to be encoded in base64" do
|
60
|
+
encoded = CGI.escape(Base64.encode64("resize=20x20&source=http://example.com/dog.jpg"))
|
61
|
+
options = Options.new "/convert", "_" => encoded
|
62
|
+
options.resize.should == "20x20"
|
63
|
+
options.source.should == "http://example.com/dog.jpg"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow the path to be encoded in base64" do
|
67
|
+
encoded = CGI.escape(Base64.encode64("resize/20x20/source/http%3A%2F%2Fexample.com%2Fdog.jpg"))
|
68
|
+
options = Options.new "/convert/-/#{encoded}", {}
|
69
|
+
options.resize.should == "20x20"
|
70
|
+
options.source.should == "http://example.com/dog.jpg"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "quality" do
|
75
|
+
it "should be set to 0 if it's less than 0" do
|
76
|
+
Options.new("/convert", "quality" => "-39").quality.should == "0"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should be set to 100 if it's > 100" do
|
80
|
+
Options.new("/convert", "quality" => "293").quality.should == "100"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should not change if it's >= 0 <= 100" do
|
84
|
+
Options.new("/convert", "quality" => "59").quality.should == "59"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Server" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
RSpec::Matchers.define :succeed do
|
7
|
+
match do |actual|
|
8
|
+
actual.status == 200
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec::Matchers.define :fail do
|
13
|
+
match do |actual|
|
14
|
+
actual.status == 500
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def app
|
19
|
+
@app ||= Server.new
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when converting" do
|
23
|
+
it "should send back the right result" do
|
24
|
+
app.stub!(:config) { |sym| nil }
|
25
|
+
get("/convert/resize/10x20/source/#{escaped_test_image_url}").should succeed
|
26
|
+
Compare.new(response_body_as_file, test_image_path("10x20")).execute.should == "0"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when identifying" do
|
31
|
+
it "should send back information about the image" do
|
32
|
+
app.stub!(:config) { |sym| nil }
|
33
|
+
get "/identify/source/#{escaped_test_image_url}"
|
34
|
+
last_response.body.should =~ /Format: PNG.*Geometry: 200x116\+0\+0/m
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when signature is required" do
|
39
|
+
before do
|
40
|
+
@secret = "SEEKRET"
|
41
|
+
app.stub!(:config) { |sym| {:signature_required => "true", :signature_secret => @secret}[sym] }
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should fail if the signature is missing" do
|
45
|
+
get("/convert/resize/10x20/source/#{escaped_test_image_url}").should fail
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should fail if the signature is incorrect" do
|
49
|
+
url = "/convert/resize/10x20/source/#{escaped_test_image_url}"
|
50
|
+
signature = "BAD"
|
51
|
+
get("#{url}?signature=#{signature}").should fail
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should work if the signature is correct" do
|
55
|
+
url = "/convert/resize/10x20/source/#{escaped_test_image_url}"
|
56
|
+
signature = Signature.create(url, @secret)
|
57
|
+
get("#{url}?signature=#{CGI.escape(signature)}").should succeed
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should work if the signature is part of the path" do
|
61
|
+
url = "/convert/resize/10x20/source/#{escaped_test_image_url}"
|
62
|
+
signature = Signature.create(url, @secret)
|
63
|
+
get("#{url}/signature/#{URI.escape(signature)}").should succeed
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when limiting to certain domains" do
|
68
|
+
before do
|
69
|
+
app.stub!(:config) { |sym| {:allowed_domains => " example.com ,example.org"}[sym] }
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should parse the allowed domains" do
|
73
|
+
app.send(:allowed_domains).should =~ ["example.com", "example.org"]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should only examine the second-level domain" do
|
77
|
+
app.send(:url_to_domain, "http://foo.bar.example.com/something").should == "example.com"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should fail if the source domain is not in the allowed domains" do
|
81
|
+
get("/convert/resize/10x20/source/#{CGI.escape('http://example.net/dog.jpg')}").should fail
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should pass if the source domain is in the allowed domains" do
|
85
|
+
get("/convert/resize/10x20/source/#{CGI.escape('http://example.org/dog.jpg')}").should succeed
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when limiting to a maximum size" do
|
90
|
+
before do
|
91
|
+
app.stub!(:config) { |sym| { :max_size => "50" }[sym] }
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should parse out the larger dimension" do
|
95
|
+
app.send(:requested_size, "10x50").should == 50
|
96
|
+
app.send(:requested_size, "50x50").should == 50
|
97
|
+
app.send(:requested_size, "50").should == 50
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should pass when converting to a smaller size" do
|
101
|
+
get("/convert/resize/20x20/source/#{escaped_test_image_url}").should succeed
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should pass when converting to the max size" do
|
105
|
+
get("/convert/resize/50x50/source/#{escaped_test_image_url}").should succeed
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should fail when converting to a larger size" do
|
109
|
+
get("/convert/resize/50x51/source/#{escaped_test_image_url}").should fail
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should pass when thumbnailing to a smaller size" do
|
113
|
+
get("/convert/thumbnail/20x20/source/#{escaped_test_image_url}").should succeed
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should fail when thumbnailing to a larger size" do
|
117
|
+
get("/convert/thumbnail/50x51/source/#{escaped_test_image_url}").should fail
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Signature do
|
4
|
+
describe "#create" do
|
5
|
+
it "should create a signature from a query string" do
|
6
|
+
Signature.create("/convert?src=http://www.example.com/dog.jpg&resize=400x400&signature=AAA&key=BBB", "SECRET").should_not be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should ignore the signature param" do
|
10
|
+
Signature.create("/convert?src=SRC&resize=10x10&signature=SIG&key=KEY", "SECRET").should == Signature.create("/convert?src=SRC&resize=10x10&key=KEY", "SECRET")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#correct?" do
|
15
|
+
it "should validate a signature" do
|
16
|
+
Signature.correct?(Signature.create("/convert?src=SRC&resize=10x10&key=KEY", "SECRET"), "/convert?src=SRC&resize=10x10&key=KEY", "SECRET").should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return false if signature is nil" do
|
20
|
+
Signature.correct?(nil, "/convert?src=SRC&resize=10x10&key=KEY", "SECRET").should be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should handle URL-safe signatures" do
|
24
|
+
Signature.correct?("_v70E0zfdcRR4cJehS2mhvqJ-8s=", "YLANEBHFSJGCAWKDNCKWEKJRXKPMYU", "SECRET").should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should handle non-URL-safe signatures" do
|
28
|
+
Signature.correct?("k7DMQ/G8YAsbSovX+mDFjlHHMjo=", "YPMMYCRRECCCIPSXPDDFIJFSINOIRC", "SECRET").should be_true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#remove_signature_from" do
|
33
|
+
it "should remove the signature when it's the only query param" do
|
34
|
+
Signature.remove_signature_from("/convert/a/apple/b/banana?signature=SIG").should ==
|
35
|
+
"/convert/a/apple/b/banana"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should remove the signature from the beginning of the query string" do
|
39
|
+
Signature.remove_signature_from("/convert/a/apple/b/banana?signature=SIG&c=cherry&d=donut").should ==
|
40
|
+
"/convert/a/apple/b/banana?c=cherry&d=donut"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should remove the signature from the middle of the query string" do
|
44
|
+
Signature.remove_signature_from("/convert/a/apple/b/banana?c=cherry&signature=SIG&d=donut").should ==
|
45
|
+
"/convert/a/apple/b/banana?c=cherry&d=donut"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should remove the signature from the end of the query string" do
|
49
|
+
Signature.remove_signature_from("/convert/a/apple/b/banana?c=cherry&d=donut&signature=SIG").should ==
|
50
|
+
"/convert/a/apple/b/banana?c=cherry&d=donut"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should remove the signature from the beginning of the path" do
|
54
|
+
Signature.remove_signature_from("/convert/signature/SIG/a/apple/b/banana?c=cherry&d=donut").should ==
|
55
|
+
"/convert/a/apple/b/banana?c=cherry&d=donut"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should remove the signature from the middle of the path" do
|
59
|
+
Signature.remove_signature_from("/convert/a/apple/signature/SIG/b/banana?c=cherry&d=donut").should ==
|
60
|
+
"/convert/a/apple/b/banana?c=cherry&d=donut"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should remove the signature from the end of the path" do
|
64
|
+
Signature.remove_signature_from("/convert/a/apple/b/banana/signature/SIG?c=cherry&d=donut").should ==
|
65
|
+
"/convert/a/apple/b/banana?c=cherry&d=donut"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should remove the signature from the end of the path when there's no query string" do
|
69
|
+
Signature.remove_signature_from("/convert/a/apple/b/banana/signature/SIG").should ==
|
70
|
+
"/convert/a/apple/b/banana"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Bundler.require :test
|
2
|
+
|
3
|
+
Dir.glob(File.join(File.dirname(__FILE__), "..", "lib", "**", "*.rb")).each {|f| require f }
|
4
|
+
|
5
|
+
def response_body_as_file
|
6
|
+
result_file = Tempfile.new("request_spec")
|
7
|
+
result_file.write(last_response.body)
|
8
|
+
result_file.close
|
9
|
+
result_file
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_image_path(size=nil)
|
13
|
+
size_suffix = size.nil? ? "" : "_#{size}"
|
14
|
+
File.expand_path(File.dirname(__FILE__) + "/../public/sample#{size_suffix}.png")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_image_url
|
18
|
+
"file://#{test_image_path}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def escaped_test_image_url
|
22
|
+
CGI.escape test_image_url
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: imageproxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Erik Hanson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-08 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rack
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: mime-types
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: heroku
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: shotgun
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rack-test
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: awesome_print
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
type: :development
|
102
|
+
prerelease: false
|
103
|
+
version_requirements: *id008
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: jeweler
|
106
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: "0"
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: *id009
|
115
|
+
description: A image processing proxy server, written in Ruby as a Rack application. Requires ImageMagick.
|
116
|
+
email: erik@eahanson.com
|
117
|
+
executables: []
|
118
|
+
|
119
|
+
extensions: []
|
120
|
+
|
121
|
+
extra_rdoc_files:
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.mdown
|
124
|
+
files:
|
125
|
+
- .document
|
126
|
+
- Gemfile
|
127
|
+
- Gemfile.lock
|
128
|
+
- LICENSE.txt
|
129
|
+
- README.mdown
|
130
|
+
- Rakefile
|
131
|
+
- VERSION
|
132
|
+
- config.ru
|
133
|
+
- imageproxy.gemspec
|
134
|
+
- imageproxy.rb
|
135
|
+
- lib/command.rb
|
136
|
+
- lib/compare.rb
|
137
|
+
- lib/convert.rb
|
138
|
+
- lib/identify.rb
|
139
|
+
- lib/options.rb
|
140
|
+
- lib/selftest.rb
|
141
|
+
- lib/server.rb
|
142
|
+
- lib/signature.rb
|
143
|
+
- public/background.png
|
144
|
+
- public/sample.png
|
145
|
+
- public/sample_10x20.png
|
146
|
+
- spec/command_spec.rb
|
147
|
+
- spec/convert_spec.rb
|
148
|
+
- spec/options_spec.rb
|
149
|
+
- spec/server_spec.rb
|
150
|
+
- spec/signature_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
has_rdoc: true
|
153
|
+
homepage: http://github.com/eahanson/imageproxy
|
154
|
+
licenses:
|
155
|
+
- MIT
|
156
|
+
post_install_message:
|
157
|
+
rdoc_options: []
|
158
|
+
|
159
|
+
require_paths:
|
160
|
+
- lib
|
161
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
162
|
+
none: false
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
hash: -3683020924727670917
|
167
|
+
segments:
|
168
|
+
- 0
|
169
|
+
version: "0"
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
+
none: false
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: "0"
|
176
|
+
requirements: []
|
177
|
+
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 1.6.2
|
180
|
+
signing_key:
|
181
|
+
specification_version: 3
|
182
|
+
summary: A image processing proxy server, written in Ruby as a Rack application.
|
183
|
+
test_files: []
|
184
|
+
|