dragonfly 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dragonfly might be problematic. Click here for more details.
- data/History.md +11 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/dragonfly.gemspec +3 -2
- data/extra_docs/Configuration.md +5 -0
- data/extra_docs/DataStorage.md +1 -1
- data/extra_docs/Heroku.md +2 -2
- data/extra_docs/ImageMagick.md +10 -0
- data/extra_docs/Models.md +5 -0
- data/extra_docs/Rails2.md +3 -3
- data/extra_docs/Rails3.md +3 -3
- data/lib/dragonfly/app.rb +13 -0
- data/lib/dragonfly/data_storage.rb +3 -2
- data/lib/dragonfly/data_storage/file_data_store.rb +1 -2
- data/lib/dragonfly/data_storage/s3data_store.rb +18 -9
- data/lib/dragonfly/image_magick/processor.rb +12 -13
- data/lib/dragonfly/job_definitions.rb +4 -0
- data/lib/dragonfly/rails/images.rb +2 -2
- data/lib/dragonfly/response.rb +1 -1
- data/lib/dragonfly/server.rb +19 -3
- data/spec/dragonfly/app_spec.rb +20 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +10 -4
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +31 -16
- data/spec/dragonfly/image_magick/processor_spec.rb +15 -2
- data/spec/dragonfly/job_definitions_spec.rb +22 -0
- data/spec/dragonfly/job_endpoint_spec.rb +6 -0
- data/spec/dragonfly/server_spec.rb +74 -58
- data/spec/support/image_matchers.rb +10 -0
- data/spec/test_imagemagick.ru +49 -0
- data/yard/templates/default/fulldoc/html/css/common.css +1 -1
- data/yard/templates/default/layout/html/layout.erb +1 -1
- metadata +4 -3
data/History.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
0.9.1 (2011-05-11)
|
2
|
+
==================
|
3
|
+
Features
|
4
|
+
--------
|
5
|
+
- Added reflection methods `app.processor_methods`, `app.generator_methods` and `app.job_methods`
|
6
|
+
|
7
|
+
Fixes
|
8
|
+
-----
|
9
|
+
- Improved performance of `resize_and_crop` method, using imagemagick built-in '^' operator
|
10
|
+
- Improved server security validations
|
11
|
+
|
1
12
|
0.9.0 (2011-04-27)
|
2
13
|
==================
|
3
14
|
Features
|
data/README.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.1
|
data/dragonfly.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dragonfly}
|
8
|
-
s.version = "0.9.
|
8
|
+
s.version = "0.9.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Mark Evans"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-05-11}
|
13
13
|
s.description = %q{Dragonfly is a framework that enables on-the-fly processing for any content type.
|
14
14
|
It is especially suited to image handling. Its uses range from image thumbnails to standard attachments to on-demand text generation.}
|
15
15
|
s.email = %q{mark@new-bamboo.co.uk}
|
@@ -172,6 +172,7 @@ Gem::Specification.new do |s|
|
|
172
172
|
"spec/support/argument_matchers.rb",
|
173
173
|
"spec/support/image_matchers.rb",
|
174
174
|
"spec/support/simple_matchers.rb",
|
175
|
+
"spec/test_imagemagick.ru",
|
175
176
|
"yard/handlers/configurable_attr_handler.rb",
|
176
177
|
"yard/setup.rb",
|
177
178
|
"yard/templates/default/fulldoc/html/css/common.css",
|
data/extra_docs/Configuration.md
CHANGED
@@ -81,6 +81,11 @@ Where is configuration done?
|
|
81
81
|
In Rails, it should be done in an initializer, e.g. 'config/initializers/dragonfly.rb'.
|
82
82
|
Otherwise it should be done anywhere where general setup is done, early on.
|
83
83
|
|
84
|
+
Reflecting on configuration
|
85
|
+
---------------------------
|
86
|
+
There are a few methods you can call on the `app` to see what processors etc. are registered: `processor_methods`, `generator_methods`
|
87
|
+
and `job_methods`.
|
88
|
+
|
84
89
|
Saved configurations
|
85
90
|
====================
|
86
91
|
Saved configurations are useful if you often configure the app the same way.
|
data/extra_docs/DataStorage.md
CHANGED
@@ -83,7 +83,7 @@ To configure with the {Dragonfly::DataStorage::S3DataStore S3DataStore}:
|
|
83
83
|
|
84
84
|
app.datastore = Dragonfly::DataStorage::S3DataStore.new
|
85
85
|
|
86
|
-
app.datastore.configure do |
|
86
|
+
app.datastore.configure do |c|
|
87
87
|
c.bucket_name = 'my_bucket'
|
88
88
|
c.access_key_id = 'salfjasd34u23'
|
89
89
|
c.secret_access_key = '8u2u3rhkhfo23...'
|
data/extra_docs/Heroku.md
CHANGED
@@ -6,7 +6,7 @@ The default configuration won't work out of the box for Heroku, because
|
|
6
6
|
- Heroku doesn't allow saving files to the filesystem (although it does use tempfiles)
|
7
7
|
- We won't need {http://tomayko.com/src/rack-cache/ Rack::Cache} on Heroku because it already uses the caching proxy {http://varnish.projects.linpro.no/ Varnish}, which we can make use of
|
8
8
|
|
9
|
-
Instead of the normal {
|
9
|
+
Instead of the normal {file:DataStorage#File\_datastore FileDataStore}, we can use the {file:DataStorage#S3\_datastore S3DataStore}.
|
10
10
|
|
11
11
|
Assuming you have an S3 account set up...
|
12
12
|
|
@@ -45,5 +45,5 @@ Now you can benefit from super-fast images served straight from Heroku's cache!
|
|
45
45
|
|
46
46
|
**NOTE**: HEROKU'S CACHE IS CLEARED EVERY TIME YOU DEPLOY!!!
|
47
47
|
|
48
|
-
If this is an issue, you may want to look into storing thumbnails on S3 (see {file:
|
48
|
+
If this is an issue, you may want to look into storing thumbnails on S3 (see {file:ServingRemotely}), or maybe an after-deploy hook for hitting specific Dragonfly urls you want to cache, etc.
|
49
49
|
It won't be a problem for most sites though.
|
data/extra_docs/ImageMagick.md
CHANGED
@@ -124,3 +124,13 @@ You can use `padding-top`, `padding-left`, etc., as well as the standard css sho
|
|
124
124
|
|
125
125
|
An alternative for `:font_family` is `:font` (see {http://www.imagemagick.org/script/command-line-options.php#font}), which could be a complete filename.
|
126
126
|
Available fonts are those available on your system.
|
127
|
+
|
128
|
+
Configuration
|
129
|
+
-------------
|
130
|
+
There are some options that can be set, e.g. if the imagemagick convert command can't be found:
|
131
|
+
|
132
|
+
app.configure do |c|
|
133
|
+
c.convert_command = "/opt/local/bin/convert" # defaults to "convert"
|
134
|
+
c.identify_command = "/opt/local/bin/convert" # defaults to "convert"
|
135
|
+
c.log_commands = true # defaults to false
|
136
|
+
end
|
data/extra_docs/Models.md
CHANGED
@@ -208,6 +208,11 @@ Validations
|
|
208
208
|
The property argument of `validates_property` will generally be one of the registered analyser properties as described in {file:Analysers.md Analysers}.
|
209
209
|
However it would actually work for arbitrary properties, including those of non-dragonfly model attributes.
|
210
210
|
|
211
|
+
`validates_property` can also take a proc for the message, yielding the actual value and the model
|
212
|
+
|
213
|
+
validates_property :width, :of => :cover_image, :in => (0..400),
|
214
|
+
:message => proc{|actual, model| "Unlucky #{model.title} - was #{actual}" }
|
215
|
+
|
211
216
|
Name and extension
|
212
217
|
------------------
|
213
218
|
If the object assigned is a file, or responds to `original_filename` (as is the case with file uploads in Rails, etc.), then `name` will be set.
|
data/extra_docs/Rails2.md
CHANGED
@@ -25,11 +25,11 @@ config/initializers/dragonfly.rb:
|
|
25
25
|
|
26
26
|
environment.rb:
|
27
27
|
|
28
|
-
config.middleware.
|
28
|
+
config.middleware.insert 0, 'Dragonfly::Middleware', :images, '/media'
|
29
29
|
config.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
|
30
30
|
:verbose => true,
|
31
|
-
:metastore => "file:#{Rails.root}/tmp/dragonfly/cache/meta",
|
32
|
-
:entitystore => "file:#{Rails.root}/tmp/dragonfly/cache/body"
|
31
|
+
:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"),
|
32
|
+
:entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
|
33
33
|
}
|
34
34
|
|
35
35
|
Gems
|
data/extra_docs/Rails3.md
CHANGED
@@ -26,14 +26,14 @@ application.rb:
|
|
26
26
|
config.middleware.insert 0, 'Dragonfly::Middleware', :images
|
27
27
|
config.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
|
28
28
|
:verbose => true,
|
29
|
-
:metastore => "file:#{Rails.root}/tmp/dragonfly/cache/meta",
|
30
|
-
:entitystore => "file:#{Rails.root}/tmp/dragonfly/cache/body"
|
29
|
+
:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"),
|
30
|
+
:entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
|
31
31
|
}
|
32
32
|
|
33
33
|
Gemfile
|
34
34
|
-------
|
35
35
|
|
36
|
-
gem 'dragonfly', '~>0.9.
|
36
|
+
gem 'dragonfly', '~>0.9.1'
|
37
37
|
gem 'rack-cache', :require => 'rack/cache'
|
38
38
|
|
39
39
|
Capistrano
|
data/lib/dragonfly/app.rb
CHANGED
@@ -145,6 +145,19 @@ module Dragonfly
|
|
145
145
|
end
|
146
146
|
end
|
147
147
|
|
148
|
+
# Reflection
|
149
|
+
def processor_methods
|
150
|
+
processor.functions.keys
|
151
|
+
end
|
152
|
+
|
153
|
+
def generator_methods
|
154
|
+
generator.functions.keys
|
155
|
+
end
|
156
|
+
|
157
|
+
def job_methods
|
158
|
+
job_definitions.definition_names
|
159
|
+
end
|
160
|
+
|
148
161
|
# Deprecated methods
|
149
162
|
def url_path_prefix=(thing)
|
150
163
|
raise NoMethodError, "url_path_prefix is deprecated - please use url_format, e.g. url_format = '/media/:job/:basename.:format' - see docs for more details"
|
@@ -2,8 +2,9 @@ module Dragonfly
|
|
2
2
|
module DataStorage
|
3
3
|
|
4
4
|
# Exceptions
|
5
|
-
class
|
6
|
-
class
|
5
|
+
class BadUID < RuntimeError; end
|
6
|
+
class DataNotFound < RuntimeError; end
|
7
|
+
class UnableToStore < RuntimeError; end
|
7
8
|
|
8
9
|
end
|
9
10
|
end
|
@@ -6,7 +6,6 @@ module Dragonfly
|
|
6
6
|
class FileDataStore
|
7
7
|
|
8
8
|
# Exceptions
|
9
|
-
class BadUID < RuntimeError; end
|
10
9
|
class UnableToFormUrl < RuntimeError; end
|
11
10
|
|
12
11
|
include Configurable
|
@@ -139,7 +138,7 @@ module Dragonfly
|
|
139
138
|
end
|
140
139
|
|
141
140
|
def validate_uid!(uid)
|
142
|
-
raise BadUID, "tried to
|
141
|
+
raise BadUID, "tried to retrieve uid #{uid.inspect}" if uid.blank? || uid['../']
|
143
142
|
end
|
144
143
|
|
145
144
|
end
|
@@ -37,12 +37,14 @@ module Dragonfly
|
|
37
37
|
headers = opts[:headers] || {}
|
38
38
|
uid = opts[:path] || generate_uid(meta[:name] || temp_object.original_filename || 'file')
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
rescuing_socket_errors do
|
41
|
+
if use_filesystem
|
42
|
+
temp_object.file do |f|
|
43
|
+
storage.put_object(bucket_name, uid, f, full_storage_headers(headers, meta))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
storage.put_object(bucket_name, uid, temp_object.data, full_storage_headers(headers, meta))
|
43
47
|
end
|
44
|
-
else
|
45
|
-
storage.put_object(bucket_name, uid, temp_object.data, full_storage_headers(headers, meta))
|
46
48
|
end
|
47
49
|
|
48
50
|
uid
|
@@ -50,7 +52,7 @@ module Dragonfly
|
|
50
52
|
|
51
53
|
def retrieve(uid)
|
52
54
|
ensure_configured
|
53
|
-
response = storage.get_object(bucket_name, uid)
|
55
|
+
response = rescuing_socket_errors{ storage.get_object(bucket_name, uid) }
|
54
56
|
[
|
55
57
|
response.body,
|
56
58
|
parse_s3_metadata(response.headers)
|
@@ -60,7 +62,7 @@ module Dragonfly
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def destroy(uid)
|
63
|
-
storage.delete_object(bucket_name, uid)
|
65
|
+
rescuing_socket_errors{ storage.delete_object(bucket_name, uid) }
|
64
66
|
rescue Excon::Errors::NotFound => e
|
65
67
|
raise DataNotFound, "#{e} - #{uid}"
|
66
68
|
end
|
@@ -87,7 +89,7 @@ module Dragonfly
|
|
87
89
|
end
|
88
90
|
|
89
91
|
def bucket_exists?
|
90
|
-
storage.get_bucket_location(bucket_name)
|
92
|
+
rescuing_socket_errors{ storage.get_bucket_location(bucket_name) }
|
91
93
|
true
|
92
94
|
rescue Excon::Errors::NotFound => e
|
93
95
|
false
|
@@ -106,7 +108,7 @@ module Dragonfly
|
|
106
108
|
|
107
109
|
def ensure_bucket_initialized
|
108
110
|
unless @bucket_initialized
|
109
|
-
storage.put_bucket(bucket_name, 'LocationConstraint' => region) unless bucket_exists?
|
111
|
+
rescuing_socket_errors{ storage.put_bucket(bucket_name, 'LocationConstraint' => region) } unless bucket_exists?
|
110
112
|
@bucket_initialized = true
|
111
113
|
end
|
112
114
|
end
|
@@ -134,6 +136,13 @@ module Dragonfly
|
|
134
136
|
REGIONS.keys
|
135
137
|
end
|
136
138
|
|
139
|
+
def rescuing_socket_errors(&block)
|
140
|
+
yield
|
141
|
+
rescue Excon::Errors::SocketError => e
|
142
|
+
storage.reload
|
143
|
+
yield
|
144
|
+
end
|
145
|
+
|
137
146
|
end
|
138
147
|
|
139
148
|
end
|
@@ -35,8 +35,9 @@ module Dragonfly
|
|
35
35
|
y = "#{opts[:y] || 0}"
|
36
36
|
y = '+' + y unless y[/^[+-]/]
|
37
37
|
repage = opts[:repage] == false ? '' : '+repage'
|
38
|
+
resize = opts[:resize]
|
38
39
|
|
39
|
-
convert(temp_object, "-
|
40
|
+
convert(temp_object, "#{"-resize #{resize} " if resize}#{"-gravity #{gravity} " if gravity}-crop #{width}x#{height}#{x}#{y} #{repage}")
|
40
41
|
end
|
41
42
|
|
42
43
|
def flip(temp_object)
|
@@ -53,20 +54,18 @@ module Dragonfly
|
|
53
54
|
alias grayscale greyscale
|
54
55
|
|
55
56
|
def resize_and_crop(temp_object, opts={})
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
gravity = opts[:gravity] || 'c'
|
63
|
-
|
64
|
-
if width != current_width || height != current_height
|
65
|
-
scale = [width.to_f / current_width, height.to_f / current_height].max
|
66
|
-
temp_object = TempObject.new(resize(temp_object, "#{(scale * current_width).ceil}x#{(scale * current_height).ceil}"))
|
57
|
+
if !opts[:width] && !opts[:height]
|
58
|
+
return temp_object
|
59
|
+
elsif !opts[:width] || !opts[:height]
|
60
|
+
attrs = identify(temp_object)
|
61
|
+
opts[:width] ||= attrs[:width]
|
62
|
+
opts[:height] ||= attrs[:height]
|
67
63
|
end
|
68
64
|
|
69
|
-
|
65
|
+
opts[:gravity] ||= 'c'
|
66
|
+
|
67
|
+
opts[:resize] = "#{opts[:width]}x#{opts[:height]}^^"
|
68
|
+
crop(temp_object, opts)
|
70
69
|
end
|
71
70
|
|
72
71
|
def rotate(temp_object, amount, opts={})
|
@@ -19,8 +19,8 @@ begin
|
|
19
19
|
require 'rack/cache'
|
20
20
|
Rails.application.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
|
21
21
|
:verbose => true,
|
22
|
-
:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded
|
23
|
-
:entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
|
22
|
+
:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded in case of spaces
|
23
|
+
:entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
|
24
24
|
}
|
25
25
|
rescue LoadError => e
|
26
26
|
app.log.warn("Warning: couldn't find rack-cache for caching dragonfly content")
|
data/lib/dragonfly/response.rb
CHANGED
@@ -24,7 +24,7 @@ module Dragonfly
|
|
24
24
|
job.apply
|
25
25
|
[200, success_headers, job.result]
|
26
26
|
end
|
27
|
-
rescue DataStorage::DataNotFound => e
|
27
|
+
rescue DataStorage::DataNotFound, DataStorage::BadUID => e
|
28
28
|
app.log.warn(e.message)
|
29
29
|
[404, {"Content-Type" => 'text/plain'}, ['Not found']]
|
30
30
|
end
|
data/lib/dragonfly/server.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
module Dragonfly
|
2
2
|
class Server
|
3
3
|
|
4
|
+
# Exceptions
|
5
|
+
class JobNotAllowed < RuntimeError; end
|
6
|
+
|
4
7
|
include Loggable
|
5
8
|
include Configurable
|
6
9
|
|
10
|
+
configurable_attr :allow_fetch_file, false
|
11
|
+
configurable_attr :allow_fetch_url, false
|
7
12
|
configurable_attr :dragonfly_url, '/dragonfly'
|
8
13
|
configurable_attr :protect_from_dos_attacks, false
|
9
14
|
configurable_attr :url_format, '/:job/:basename.:format'
|
@@ -28,6 +33,7 @@ module Dragonfly
|
|
28
33
|
dragonfly_response
|
29
34
|
elsif (params = url_mapper.params_for(env["PATH_INFO"], env["QUERY_STRING"])) && params['job']
|
30
35
|
job = Job.deserialize(params['job'], app)
|
36
|
+
validate_job!(job)
|
31
37
|
job.validate_sha!(params['sha']) if protect_from_dos_attacks
|
32
38
|
response = Response.new(job, env)
|
33
39
|
catch(:halt) do
|
@@ -39,13 +45,16 @@ module Dragonfly
|
|
39
45
|
else
|
40
46
|
[404, {'Content-Type' => 'text/plain', 'X-Cascade' => 'pass'}, ['Not found']]
|
41
47
|
end
|
42
|
-
rescue Serializer::BadString, Job::InvalidArray => e
|
43
|
-
log.warn(e.message)
|
44
|
-
[404, {'Content-Type' => 'text/plain'}, ['Not found']]
|
45
48
|
rescue Job::NoSHAGiven => e
|
46
49
|
[400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
|
47
50
|
rescue Job::IncorrectSHA => e
|
48
51
|
[400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
|
52
|
+
rescue JobNotAllowed => e
|
53
|
+
log.warn(e.message)
|
54
|
+
[403, {"Content-Type" => 'text/plain'}, ["Forbidden"]]
|
55
|
+
rescue Serializer::BadString, Job::InvalidArray => e
|
56
|
+
log.warn(e.message)
|
57
|
+
[404, {'Content-Type' => 'text/plain'}, ['Not found']]
|
49
58
|
end
|
50
59
|
|
51
60
|
def url_for(job, opts={})
|
@@ -99,5 +108,12 @@ module Dragonfly
|
|
99
108
|
]
|
100
109
|
end
|
101
110
|
|
111
|
+
def validate_job!(job)
|
112
|
+
if job.fetch_file_step && !allow_fetch_file ||
|
113
|
+
job.fetch_url_step && !allow_fetch_url
|
114
|
+
raise JobNotAllowed, "Dragonfly Server doesn't allow requesting job with steps #{job.steps.inspect}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
102
118
|
end
|
103
119
|
end
|
data/spec/dragonfly/app_spec.rb
CHANGED
@@ -132,4 +132,24 @@ describe Dragonfly::App do
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
+
describe "reflection methods" do
|
136
|
+
before(:each) do
|
137
|
+
@app = test_app.configure do |c|
|
138
|
+
c.processor.add(:milk){}
|
139
|
+
c.generator.add(:butter){}
|
140
|
+
c.job(:bacon){}
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
it "should return processor methods" do
|
145
|
+
@app.processor_methods.should == [:milk]
|
146
|
+
end
|
147
|
+
it "should return generator methods" do
|
148
|
+
@app.generator_methods.should == [:butter]
|
149
|
+
end
|
150
|
+
it "should return job methods" do
|
151
|
+
@app.job_methods.should == [:bacon]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
135
155
|
end
|
@@ -168,10 +168,16 @@ describe Dragonfly::DataStorage::FileDataStore do
|
|
168
168
|
meta.should == {:dog => 'food'}
|
169
169
|
end
|
170
170
|
|
171
|
-
it "should raise
|
171
|
+
it "should raise a BadUID error if the file path has ../ in it" do
|
172
172
|
expect{
|
173
173
|
@data_store.retrieve('jelly_beans/../are/good')
|
174
|
-
}.to raise_error(Dragonfly::DataStorage::
|
174
|
+
}.to raise_error(Dragonfly::DataStorage::BadUID)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should not raise a BadUID error if the file path has .. but not ../ in it" do
|
178
|
+
expect{
|
179
|
+
@data_store.retrieve('jelly_beans..good')
|
180
|
+
}.to raise_error(Dragonfly::DataStorage::DataNotFound)
|
175
181
|
end
|
176
182
|
end
|
177
183
|
|
@@ -199,10 +205,10 @@ describe Dragonfly::DataStorage::FileDataStore do
|
|
199
205
|
File.exist?("#{@data_store.root_path}/#{uid}.extra").should be_false
|
200
206
|
end
|
201
207
|
|
202
|
-
it "should raise an error if the file path has
|
208
|
+
it "should raise an error if the file path has ../ in it" do
|
203
209
|
expect{
|
204
210
|
@data_store.destroy('jelly_beans/../are/good')
|
205
|
-
}.to raise_error(Dragonfly::DataStorage::
|
211
|
+
}.to raise_error(Dragonfly::DataStorage::BadUID)
|
206
212
|
end
|
207
213
|
end
|
208
214
|
|
@@ -89,25 +89,40 @@ describe Dragonfly::DataStorage::S3DataStore do
|
|
89
89
|
uid = @data_store.store(temp_object)
|
90
90
|
@data_store.retrieve(uid).first.should == "gollum"
|
91
91
|
end
|
92
|
-
end
|
93
|
-
|
94
|
-
if enabled # Fog.mock! doesn't work consistently with real one here
|
95
92
|
|
96
|
-
|
97
|
-
|
98
|
-
|
93
|
+
if enabled # Fog.mock! doesn't act consistently here
|
94
|
+
it "should reset the connection and try again if Fog throws a socket EOFError" do
|
95
|
+
temp_object = Dragonfly::TempObject.new('gollum')
|
96
|
+
@data_store.storage.should_receive(:put_object).exactly(:once).and_raise(Excon::Errors::SocketError.new(EOFError.new))
|
97
|
+
@data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything, hash_including)
|
98
|
+
@data_store.store(temp_object)
|
99
99
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
100
|
+
|
101
|
+
it "should just let it raise if Fog throws a socket EOFError again" do
|
102
|
+
temp_object = Dragonfly::TempObject.new('gollum')
|
103
|
+
@data_store.storage.should_receive(:put_object).and_raise(Excon::Errors::SocketError.new(EOFError.new))
|
104
|
+
@data_store.storage.should_receive(:put_object).and_raise(Excon::Errors::SocketError.new(EOFError.new))
|
105
|
+
expect{
|
106
|
+
@data_store.store(temp_object)
|
107
|
+
}.to raise_error(Excon::Errors::SocketError)
|
106
108
|
end
|
107
109
|
end
|
108
|
-
|
109
110
|
end
|
110
111
|
|
112
|
+
# Doesn't appear to raise anything right now
|
113
|
+
# describe "destroy" do
|
114
|
+
# before(:each) do
|
115
|
+
# @temp_object = Dragonfly::TempObject.new('gollum')
|
116
|
+
# end
|
117
|
+
# it "should raise an error if the data doesn't exist on destroy" do
|
118
|
+
# uid = @data_store.store(@temp_object)
|
119
|
+
# @data_store.destroy(uid)
|
120
|
+
# lambda{
|
121
|
+
# @data_store.destroy(uid)
|
122
|
+
# }.should raise_error(Dragonfly::DataStorage::DataNotFound)
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
|
111
126
|
describe "domain" do
|
112
127
|
it "should default to the US" do
|
113
128
|
@data_store.region = nil
|
@@ -183,21 +198,21 @@ describe Dragonfly::DataStorage::S3DataStore do
|
|
183
198
|
end
|
184
199
|
|
185
200
|
it "should allow configuring globally" do
|
186
|
-
@data_store.storage.should_receive(:put_object).with(
|
201
|
+
@data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything,
|
187
202
|
hash_including('x-amz-foo' => 'biscuithead')
|
188
203
|
)
|
189
204
|
@data_store.store(@temp_object)
|
190
205
|
end
|
191
206
|
|
192
207
|
it "should allow adding per-store" do
|
193
|
-
@data_store.storage.should_receive(:put_object).with(
|
208
|
+
@data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything,
|
194
209
|
hash_including('x-amz-foo' => 'biscuithead', 'hello' => 'there')
|
195
210
|
)
|
196
211
|
@data_store.store(@temp_object, :headers => {'hello' => 'there'})
|
197
212
|
end
|
198
213
|
|
199
214
|
it "should let the per-store one take precedence" do
|
200
|
-
@data_store.storage.should_receive(:put_object).with(
|
215
|
+
@data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything,
|
201
216
|
hash_including('x-amz-foo' => 'override!')
|
202
217
|
)
|
203
218
|
@data_store.store(@temp_object, :headers => {'x-amz-foo' => 'override!'})
|
@@ -103,7 +103,7 @@ describe Dragonfly::ImageMagick::Processor do
|
|
103
103
|
it "should take into account the gravity given" do
|
104
104
|
image1 = @processor.crop(@image, :width => '10', :height => '10', :gravity => 'nw')
|
105
105
|
image2 = @processor.crop(@image, :width => '10', :height => '10', :gravity => 'se')
|
106
|
-
image1.should_not
|
106
|
+
image1.should_not equal_image(image2)
|
107
107
|
end
|
108
108
|
|
109
109
|
it "should clip bits of the image outside of the requested crop area when not nw gravity" do
|
@@ -147,12 +147,25 @@ describe Dragonfly::ImageMagick::Processor do
|
|
147
147
|
image.should have_height(355)
|
148
148
|
end
|
149
149
|
|
150
|
+
it "should do nothing if called without width and height" do
|
151
|
+
image = @processor.resize_and_crop(@image)
|
152
|
+
image.should have_width(280)
|
153
|
+
image.should have_height(355)
|
154
|
+
image.should eq @image
|
155
|
+
end
|
156
|
+
|
150
157
|
it "should crop to the correct dimensions" do
|
151
158
|
image = @processor.resize_and_crop(@image, :width => '100', :height => '100')
|
152
159
|
image.should have_width(100)
|
153
160
|
image.should have_height(100)
|
154
161
|
end
|
155
162
|
|
163
|
+
it "should actually resize before cropping" do
|
164
|
+
image1 = @processor.resize_and_crop(@image, :width => '100', :height => '100')
|
165
|
+
image2 = @processor.crop(@image, :width => '100', :height => '100', :gravity => 'c')
|
166
|
+
image1.should_not equal_image(image2)
|
167
|
+
end
|
168
|
+
|
156
169
|
it "should allow cropping in one dimension" do
|
157
170
|
image = @processor.resize_and_crop(@image, :width => '100')
|
158
171
|
image.should have_width(100)
|
@@ -162,7 +175,7 @@ describe Dragonfly::ImageMagick::Processor do
|
|
162
175
|
it "should take into account the gravity given" do
|
163
176
|
image1 = @processor.resize_and_crop(@image, :width => '10', :height => '10', :gravity => 'nw')
|
164
177
|
image2 = @processor.resize_and_crop(@image, :width => '10', :height => '10', :gravity => 'se')
|
165
|
-
image1.should_not
|
178
|
+
image1.should_not equal_image(image2)
|
166
179
|
end
|
167
180
|
|
168
181
|
end
|
@@ -32,4 +32,26 @@ describe Dragonfly::JobDefinitions do
|
|
32
32
|
|
33
33
|
end
|
34
34
|
|
35
|
+
|
36
|
+
describe "#definition_names" do
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
@job_definitions = Dragonfly::JobDefinitions.new
|
40
|
+
@object = Object.new
|
41
|
+
@object.extend @job_definitions
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should provide an empty list when no jobs have been defined" do
|
45
|
+
@job_definitions.definition_names.should == []
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should contain the job name when one is defined" do
|
49
|
+
@job_definitions.add :foo do |size|
|
50
|
+
process :thumb, size
|
51
|
+
end
|
52
|
+
@job_definitions.definition_names.should eq [:foo]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
35
57
|
end
|
@@ -86,6 +86,12 @@ describe Dragonfly::JobEndpoint do
|
|
86
86
|
response.status.should == 404
|
87
87
|
end
|
88
88
|
|
89
|
+
it "should return a 404 if the datastore raises bad uid" do
|
90
|
+
@job.should_receive(:apply).and_raise(Dragonfly::DataStorage::BadUID)
|
91
|
+
response = make_request(@job)
|
92
|
+
response.status.should == 404
|
93
|
+
end
|
94
|
+
|
89
95
|
describe "ETag" do
|
90
96
|
it "should return an ETag" do
|
91
97
|
response = make_request(@job)
|
@@ -22,7 +22,7 @@ describe Dragonfly::Server do
|
|
22
22
|
@app.destroy(@uid)
|
23
23
|
end
|
24
24
|
|
25
|
-
describe "successful
|
25
|
+
describe "successful requests" do
|
26
26
|
before(:each) do
|
27
27
|
@server.url_format = '/media/:job/:name.:format'
|
28
28
|
end
|
@@ -59,74 +59,90 @@ describe Dragonfly::Server do
|
|
59
59
|
response.status.should == 200
|
60
60
|
response.body.should == 'eggs'
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
|
+
it "should return a cacheable response" do
|
64
|
+
url = "/media/#{@job.serialize}"
|
65
|
+
cache = Rack::Cache.new(@server, :entitystore => 'heap:/')
|
66
|
+
response = request(cache, url)
|
67
|
+
response.status.should == 200
|
68
|
+
response.headers['X-Rack-Cache'].should == "miss, store"
|
69
|
+
response = request(cache, url)
|
70
|
+
response.status.should == 200
|
71
|
+
response.headers['X-Rack-Cache'].should == "fresh"
|
72
|
+
end
|
63
73
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
74
|
+
it "should return successfully even if the job is in the query string" do
|
75
|
+
@server.url_format = '/'
|
76
|
+
url = "/?job=#{@job.serialize}"
|
77
|
+
response = request(@server, url)
|
78
|
+
response.status.should == 200
|
79
|
+
response.body.should == 'HELLO THERE'
|
80
|
+
end
|
70
81
|
end
|
71
82
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
83
|
+
describe "unsuccessful requests" do
|
84
|
+
it "should return a 400 if no sha given but protection on" do
|
85
|
+
@server.protect_from_dos_attacks = true
|
86
|
+
url = "/media/#{@job.serialize}"
|
87
|
+
response = request(@server, url)
|
88
|
+
response.status.should == 400
|
89
|
+
end
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
response = request(@server, url)
|
83
|
-
response.status.should == 400
|
84
|
-
end
|
85
|
-
|
86
|
-
['/media', '/media/'].each do |url|
|
87
|
-
it "should return a 404 when no job given, e.g. #{url.inspect}" do
|
91
|
+
it "should return a 400 if wrong sha given and protection on" do
|
92
|
+
@server.protect_from_dos_attacks = true
|
93
|
+
url = "/media/#{@job.serialize}?sha=asdfs"
|
88
94
|
response = request(@server, url)
|
95
|
+
response.status.should == 400
|
96
|
+
end
|
97
|
+
|
98
|
+
['/media', '/media/'].each do |url|
|
99
|
+
it "should return a 404 when no job given, e.g. #{url.inspect}" do
|
100
|
+
response = request(@server, url)
|
101
|
+
response.status.should == 404
|
102
|
+
response.body.should == 'Not found'
|
103
|
+
response.content_type.should == 'text/plain'
|
104
|
+
response.headers['X-Cascade'].should == 'pass'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should return a 404 when the url matches but doesn't correspond to a job" do
|
109
|
+
response = request(@server, '/media/sadhfasdfdsfsdf')
|
89
110
|
response.status.should == 404
|
90
111
|
response.body.should == 'Not found'
|
91
112
|
response.content_type.should == 'text/plain'
|
92
|
-
response.headers['X-Cascade'].should
|
113
|
+
response.headers['X-Cascade'].should be_nil
|
93
114
|
end
|
94
|
-
end
|
95
|
-
|
96
|
-
it "should return a 404 when the url matches but doesn't correspond to a job" do
|
97
|
-
response = request(@server, '/media/sadhfasdfdsfsdf')
|
98
|
-
response.status.should == 404
|
99
|
-
response.body.should == 'Not found'
|
100
|
-
response.content_type.should == 'text/plain'
|
101
|
-
response.headers['X-Cascade'].should be_nil
|
102
|
-
end
|
103
115
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
116
|
+
it "should return a 404 when the url isn't known at all" do
|
117
|
+
response = request(@server, '/jfasd/dsfa')
|
118
|
+
response.status.should == 404
|
119
|
+
response.body.should == 'Not found'
|
120
|
+
response.content_type.should == 'text/plain'
|
121
|
+
response.headers['X-Cascade'].should == 'pass'
|
122
|
+
end
|
111
123
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
124
|
+
it "should return a 404 when the url is a well-encoded but bad array" do
|
125
|
+
url = "/media/#{Dragonfly::Serializer.marshal_encode([[:egg, {:some => 'args'}]])}"
|
126
|
+
response = request(@server, url)
|
127
|
+
response.status.should == 404
|
128
|
+
response.body.should == 'Not found'
|
129
|
+
response.content_type.should == 'text/plain'
|
130
|
+
response.headers['X-Cascade'].should be_nil
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return a 403 Forbidden when someone uses fetch_file" do
|
134
|
+
response = request(@server, "/media/#{@app.fetch_file('/some/file.txt').serialize}")
|
135
|
+
response.status.should == 403
|
136
|
+
response.body.should == 'Forbidden'
|
137
|
+
response.content_type.should == 'text/plain'
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should return a 403 Forbidden when someone uses fetch_url" do
|
141
|
+
response = request(@server, "/media/#{@app.fetch_url('some.url').serialize}")
|
142
|
+
response.status.should == 403
|
143
|
+
response.body.should == 'Forbidden'
|
144
|
+
response.content_type.should == 'text/plain'
|
145
|
+
end
|
130
146
|
end
|
131
147
|
|
132
148
|
end
|
@@ -45,3 +45,13 @@ RSpec::Matchers.define :have_size do |size|
|
|
45
45
|
image_properties(given)[:size].should == size
|
46
46
|
end
|
47
47
|
end
|
48
|
+
|
49
|
+
RSpec::Matchers.define :equal_image do |other|
|
50
|
+
match do |given|
|
51
|
+
image_data = given.open.read
|
52
|
+
other_image_data = other.open.read
|
53
|
+
given.close
|
54
|
+
other.close
|
55
|
+
image_data == other_image_data
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
4
|
+
require 'dragonfly'
|
5
|
+
|
6
|
+
ROOT = (File.expand_path('../..', __FILE__))
|
7
|
+
APP = Dragonfly[:images].configure_with(:imagemagick).configure do |c|
|
8
|
+
c.url_format = '/images/:job'
|
9
|
+
c.allow_fetch_file = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def row(geometry)
|
13
|
+
image = APP.fetch_file(ROOT + '/samples/beach.png').thumb('100x100#')
|
14
|
+
%(<tr>
|
15
|
+
<th>#{geometry}</th>
|
16
|
+
<th><img src="#{image.url}" /></th>
|
17
|
+
<th><img src="#{image.thumb(geometry).url}" /></th>
|
18
|
+
</tr>)
|
19
|
+
end
|
20
|
+
|
21
|
+
use Dragonfly::Middleware, :images
|
22
|
+
run proc{[
|
23
|
+
200,
|
24
|
+
{'Content-Type' => 'text/html'},
|
25
|
+
[%(
|
26
|
+
<table>
|
27
|
+
<tr>
|
28
|
+
<th>Geometry</th>
|
29
|
+
<th>Original(100x100)</th>
|
30
|
+
<th>Thumb</th>
|
31
|
+
</tr>
|
32
|
+
#{[
|
33
|
+
row('80x60'),
|
34
|
+
row('80x60!'),
|
35
|
+
row('80x'),
|
36
|
+
row('x60'),
|
37
|
+
row('80x60>'),
|
38
|
+
row('80x60<'),
|
39
|
+
row('50x50%'),
|
40
|
+
row('80x60^'),
|
41
|
+
row('2000@'),
|
42
|
+
row('80x60#'),
|
43
|
+
row('80x60#ne'),
|
44
|
+
row('80x60se'),
|
45
|
+
row('80x60+5+35')
|
46
|
+
].join}
|
47
|
+
</table>
|
48
|
+
)]
|
49
|
+
]}
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: dragonfly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.9.
|
5
|
+
version: 0.9.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mark Evans
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-05-11 00:00:00 +01:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -388,6 +388,7 @@ files:
|
|
388
388
|
- spec/support/argument_matchers.rb
|
389
389
|
- spec/support/image_matchers.rb
|
390
390
|
- spec/support/simple_matchers.rb
|
391
|
+
- spec/test_imagemagick.ru
|
391
392
|
- yard/handlers/configurable_attr_handler.rb
|
392
393
|
- yard/setup.rb
|
393
394
|
- yard/templates/default/fulldoc/html/css/common.css
|
@@ -408,7 +409,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
408
409
|
requirements:
|
409
410
|
- - ">="
|
410
411
|
- !ruby/object:Gem::Version
|
411
|
-
hash:
|
412
|
+
hash: 3227660044973148793
|
412
413
|
segments:
|
413
414
|
- 0
|
414
415
|
version: "0"
|