dragonfly 0.9.13 → 0.9.14

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 CHANGED
@@ -1,3 +1,15 @@
1
+ 0.9.14 (2013-02-13)
2
+ ===================
3
+ Features
4
+ --------
5
+ - Attachment#b64_data
6
+
7
+ Fixes
8
+ -----
9
+ - Fix '+' character being converted to ' ' (revert to URI.escape instead of Rack::Utils.escape)
10
+ - Support old-style deprecated urls (with a check for malicious ones)
11
+ - Handle case where uid is an empty string
12
+
1
13
  0.9.13 (2013-01-30)
2
14
  ===================
3
15
  Changes
data/README.md CHANGED
@@ -13,7 +13,7 @@ For the lazy Rails user...
13
13
 
14
14
  ```ruby
15
15
  gem 'rack-cache', :require => 'rack/cache'
16
- gem 'dragonfly', '~>0.9.13'
16
+ gem 'dragonfly', '~>0.9.14'
17
17
  ```
18
18
 
19
19
  **Initializer** (e.g. config/initializers/dragonfly.rb):
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.13
1
+ 0.9.14
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "dragonfly"
8
- s.version = "0.9.13"
8
+ s.version = "0.9.14"
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 = "2013-01-30"
12
+ s.date = "2013-02-13"
13
13
  s.description = "Dragonfly is a framework that enables on-the-fly processing for any content type.\n It is especially suited to image handling. Its uses range from image thumbnails to standard attachments to on-demand text generation."
14
14
  s.email = "mark@new-bamboo.co.uk"
15
15
  s.extra_rdoc_files = [
@@ -178,6 +178,7 @@ Gem::Specification.new do |s|
178
178
  "spec/functional/remote_on_the_fly_spec.rb",
179
179
  "spec/functional/shell_commands_spec.rb",
180
180
  "spec/functional/to_response_spec.rb",
181
+ "spec/functional/urls_spec.rb",
181
182
  "spec/spec_helper.rb",
182
183
  "spec/support/argument_matchers.rb",
183
184
  "spec/support/image_matchers.rb",
@@ -35,7 +35,7 @@ application.rb:
35
35
  Gemfile
36
36
  -------
37
37
 
38
- gem 'dragonfly', '~>0.9.13'
38
+ gem 'dragonfly', '~>0.9.14'
39
39
  gem 'rack-cache', :require => 'rack/cache'
40
40
 
41
41
  Capistrano
@@ -11,7 +11,7 @@ module Dragonfly
11
11
 
12
12
  extend Forwardable
13
13
  def_delegators :job,
14
- :data, :to_file, :file, :tempfile, :path,
14
+ :data, :b64_data, :to_file, :file, :tempfile, :path,
15
15
  :process, :encode, :analyse,
16
16
  :meta, :meta=,
17
17
  :name, :size,
@@ -20,11 +20,11 @@ module Dragonfly
20
20
  include HasFilename
21
21
 
22
22
  alias_method :length, :size
23
-
23
+
24
24
  def initialize(model)
25
25
  @model = model
26
26
  self.uid = model_uid
27
- set_job_from_uid if uid
27
+ set_job_from_uid if uid?
28
28
  @should_run_callbacks = true
29
29
  self.class.ensure_uses_cached_magic_attributes
30
30
  end
@@ -32,7 +32,7 @@ module Dragonfly
32
32
  def app
33
33
  self.class.app
34
34
  end
35
-
35
+
36
36
  def attribute
37
37
  self.class.attribute
38
38
  end
@@ -66,7 +66,7 @@ module Dragonfly
66
66
 
67
67
  def destroy!
68
68
  destroy_previous!
69
- destroy_content(uid) if uid
69
+ destroy_content(uid) if uid?
70
70
  end
71
71
 
72
72
  def save!
@@ -95,18 +95,18 @@ module Dragonfly
95
95
  assign(encode(*args))
96
96
  self
97
97
  end
98
-
98
+
99
99
  def remote_url(opts={})
100
- app.remote_url_for(uid, opts) if uid
100
+ app.remote_url_for(uid, opts) if uid?
101
101
  end
102
-
102
+
103
103
  def apply
104
104
  job.apply
105
105
  self
106
106
  end
107
107
 
108
108
  attr_writer :should_run_callbacks
109
-
109
+
110
110
  def should_run_callbacks?
111
111
  !!@should_run_callbacks
112
112
  end
@@ -121,26 +121,26 @@ module Dragonfly
121
121
  end
122
122
 
123
123
  attr_writer :should_retain
124
-
124
+
125
125
  def should_retain?
126
126
  !!@should_retain
127
127
  end
128
-
128
+
129
129
  def retained?
130
130
  !!@retained
131
131
  end
132
-
132
+
133
133
  def destroy_retained!
134
134
  destroy_content(retained_attrs[:uid])
135
135
  end
136
-
136
+
137
137
  def retained_attrs
138
138
  attribute_keys.inject({}) do |hash, key|
139
139
  hash[key] = send(key)
140
140
  hash
141
141
  end if retained?
142
142
  end
143
-
143
+
144
144
  def retained_attrs=(attrs)
145
145
  if changed? # if already set, ignore and destroy this retained content
146
146
  destroy_content(attrs[:uid])
@@ -156,7 +156,7 @@ module Dragonfly
156
156
  self.retained = true
157
157
  end
158
158
  end
159
-
159
+
160
160
  def inspect
161
161
  "<Dragonfly Attachment uid=#{uid.inspect}, app=#{app.name.inspect}>"
162
162
  end
@@ -187,7 +187,7 @@ module Dragonfly
187
187
  end
188
188
 
189
189
  def destroy_previous!
190
- if previous_uid
190
+ if previous_uid?
191
191
  destroy_content(previous_uid)
192
192
  self.previous_uid = nil
193
193
  end
@@ -217,7 +217,7 @@ module Dragonfly
217
217
  meth = "#{attribute}_uid_will_change!"
218
218
  model.send(meth) if model.respond_to?(meth)
219
219
  end
220
-
220
+
221
221
  attr_reader :model, :uid
222
222
  attr_writer :job
223
223
  attr_accessor :previous_uid
@@ -227,6 +227,14 @@ module Dragonfly
227
227
  @uid = uid
228
228
  end
229
229
 
230
+ def uid?
231
+ !uid.nil? && !uid.empty?
232
+ end
233
+
234
+ def previous_uid?
235
+ !previous_uid.nil? && !previous_uid.empty?
236
+ end
237
+
230
238
  def magic_attributes
231
239
  self.class.magic_attributes
232
240
  end
@@ -264,11 +272,11 @@ module Dragonfly
264
272
  :model_attachment => attribute
265
273
  }
266
274
  end
267
-
275
+
268
276
  def all_extra_attributes
269
277
  magic_attributes_hash.merge(extra_attributes)
270
278
  end
271
-
279
+
272
280
  def set_job_from_uid
273
281
  self.job = app.fetch(uid)
274
282
  job.url_attrs = all_extra_attributes
@@ -276,4 +284,4 @@ module Dragonfly
276
284
 
277
285
  end
278
286
  end
279
- end
287
+ end
@@ -160,19 +160,24 @@ module Dragonfly
160
160
 
161
161
  def from_a(steps_array, app)
162
162
  unless steps_array.is_a?(Array) &&
163
- steps_array.all?{|s| s.is_a?(Array) && step_abbreviations[s.first] }
163
+ steps_array.all?{|s| s.is_a?(Array) && step_abbreviations[s.first.to_s] }
164
164
  raise InvalidArray, "can't define a job from #{steps_array.inspect}"
165
165
  end
166
166
  job = app.new_job
167
167
  steps_array.each do |step_array|
168
- step_class = step_abbreviations[step_array.shift]
168
+ step_class = step_abbreviations[step_array.shift.to_s]
169
169
  job.steps << step_class.new(job, *step_array)
170
170
  end
171
171
  job
172
172
  end
173
173
 
174
174
  def deserialize(string, app)
175
- from_a(Serializer.json_decode(string), app)
175
+ array = begin
176
+ Serializer.json_decode(string)
177
+ rescue Serializer::BadString
178
+ Serializer.marshal_decode(string) # legacy strings
179
+ end
180
+ from_a(array, app)
176
181
  end
177
182
 
178
183
  def step_abbreviations
@@ -7,6 +7,7 @@ module Dragonfly
7
7
 
8
8
  # Exceptions
9
9
  class BadString < RuntimeError; end
10
+ class MaliciousString < RuntimeError; end
10
11
 
11
12
  extend self # So we can do Serializer.b64_encode, etc.
12
13
 
@@ -16,6 +17,7 @@ module Dragonfly
16
17
 
17
18
  def b64_decode(string)
18
19
  padding_length = string.length % 4
20
+ string = string.tr('~', '/')
19
21
  Base64.decode64(string + '=' * padding_length)
20
22
  end
21
23
 
@@ -24,7 +26,9 @@ module Dragonfly
24
26
  end
25
27
 
26
28
  def marshal_decode(string)
27
- Marshal.load(b64_decode(string))
29
+ marshal_string = b64_decode(string)
30
+ raise MaliciousString, "potentially malicious marshal string #{marshal_string.inspect}" if marshal_string[/@[a-z_]/i]
31
+ Marshal.load(marshal_string)
28
32
  rescue TypeError, ArgumentError => e
29
33
  raise BadString, "couldn't decode #{string} - got #{e}"
30
34
  end
@@ -52,7 +52,7 @@ module Dragonfly
52
52
  rescue JobNotAllowed => e
53
53
  log.warn(e.message)
54
54
  [403, {"Content-Type" => 'text/plain'}, ["Forbidden"]]
55
- rescue Serializer::BadString, Job::InvalidArray => e
55
+ rescue Serializer::BadString, Serializer::MaliciousString, Job::InvalidArray => e
56
56
  log.warn(e.message)
57
57
  [404, {'Content-Type' => 'text/plain'}, ['Not found']]
58
58
  end
@@ -24,7 +24,7 @@ module Dragonfly
24
24
  params = Rack::Utils.parse_query(query)
25
25
  params_in_url.each_with_index do |var, i|
26
26
  value = md[i+1][1..-1] if md[i+1]
27
- params[var] = value && Rack::Utils.unescape(value)
27
+ params[var] = value && Utils.uri_unescape(value)
28
28
  end
29
29
  params
30
30
  end
@@ -39,7 +39,7 @@ module Dragonfly
39
39
  url = url_format.dup
40
40
  segments.each do |seg|
41
41
  value = params[seg.param]
42
- value ? url.sub!(/:[\w_]+/, Rack::Utils.escape(value.to_s)) : url.sub!(/.:[\w_]+/, '')
42
+ value ? url.sub!(/:[\w_]+/, Utils.uri_escape_segment(value.to_s)) : url.sub!(/.:[\w_]+/, '')
43
43
  params.delete(seg.param)
44
44
  end
45
45
  url << "?#{Rack::Utils.build_query(params)}" if params.any?
@@ -1,4 +1,5 @@
1
1
  require 'tempfile'
2
+ require 'uri'
2
3
 
3
4
  module Dragonfly
4
5
  module Utils
@@ -20,5 +21,13 @@ module Dragonfly
20
21
  end
21
22
  end
22
23
 
24
+ def uri_escape_segment(string)
25
+ URI.escape(string).sub('/', '%2F')
26
+ end
27
+
28
+ def uri_unescape(string)
29
+ URI.unescape(string)
30
+ end
31
+
23
32
  end
24
33
  end
@@ -124,6 +124,18 @@ describe Item do
124
124
  end
125
125
  end
126
126
 
127
+ describe "after a record with an empty uid is saved" do
128
+ before(:each) do
129
+ @item.preview_image_uid = ''
130
+ @item.save!
131
+ end
132
+
133
+ it "should not try to destroy anything on destroy" do
134
+ @app.datastore.should_not_receive(:destroy)
135
+ @item.destroy
136
+ end
137
+ end
138
+
127
139
  describe "when the uid is set manually" do
128
140
  before(:each) do
129
141
  @item.preview_image_uid = 'some_known_uid'
@@ -499,6 +499,11 @@ describe Dragonfly::Job do
499
499
  end
500
500
  end
501
501
 
502
+ it "works with symbols" do
503
+ job = Dragonfly::Job.from_a([[:f, 'some_uid']], @app)
504
+ job.steps.should match_steps([Dragonfly::Job::Fetch])
505
+ end
506
+
502
507
  [
503
508
  'f',
504
509
  ['f'],
@@ -539,6 +544,14 @@ describe Dragonfly::Job do
539
544
  process_step.name.should == :resize_and_crop
540
545
  process_step.arguments.should == [{'width' => 270, 'height' => 92, 'gravity' => 'n'}]
541
546
  end
547
+ it "works with json encoded strings" do
548
+ job = Dragonfly::Job.deserialize("W1siZiIsInNvbWVfdWlkIl1d", @app)
549
+ job.fetch_step.uid.should == 'some_uid'
550
+ end
551
+ it "works with marshal encoded strings (deprecated)" do
552
+ job = Dragonfly::Job.deserialize("BAhbBlsHSSIGZgY6BkVUSSINc29tZV91aWQGOwBU", @app)
553
+ job.fetch_step.uid.should == 'some_uid'
554
+ end
542
555
  end
543
556
 
544
557
  describe "to_app" do
@@ -5,22 +5,32 @@ describe Dragonfly::Serializer do
5
5
 
6
6
  include Dragonfly::Serializer
7
7
 
8
- [
9
- 'a',
10
- 'sdhflasd',
11
- '/2010/03/01/hello.png',
12
- '//..',
13
- 'whats/up.egg.frog',
14
- '£ñçùí;'
15
- ].each do |string|
16
- it "should encode #{string.inspect} properly with no padding/line break" do
17
- b64_encode(string).should_not =~ /\n|=/
8
+ describe "base 64 encoding/decoding" do
9
+ [
10
+ 'a',
11
+ 'sdhflasd',
12
+ '/2010/03/01/hello.png',
13
+ '//..',
14
+ 'whats/up.egg.frog',
15
+ '£ñçùí;',
16
+ '~'
17
+ ].each do |string|
18
+ it "should encode #{string.inspect} properly with no padding/line break" do
19
+ b64_encode(string).should_not =~ /\n|=/
20
+ end
21
+ it "should correctly encode and decode #{string.inspect} to the same string" do
22
+ str = b64_decode(b64_encode(string))
23
+ str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
24
+ str.should == string
25
+ end
18
26
  end
19
- it "should correctly encode and decode #{string.inspect} to the same string" do
20
- str = b64_decode(b64_encode(string))
21
- str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
22
- str.should == string
27
+
28
+ describe "b64_decode" do
29
+ it "converts (deprecated) '~' characters to '/' characters" do
30
+ b64_decode('asdf~asdf').should == b64_decode('asdf/asdf')
31
+ end
23
32
  end
33
+
24
34
  end
25
35
 
26
36
  [
@@ -55,6 +65,19 @@ describe Dragonfly::Serializer do
55
65
  marshal_decode('ahasdkjfhasdkfjh')
56
66
  }.should raise_error(Dragonfly::Serializer::BadString)
57
67
  end
68
+ describe "potentially harmful strings" do
69
+ ['_', 'hello', 'h2', '__send__', 'F'].each do |variable_name|
70
+ it "should raise an error if the string passed in is potentially harmful (e.g. contains instance variable #{variable_name})" do
71
+ class C; end
72
+ c = C.new
73
+ c.instance_eval{ instance_variable_set("@#{variable_name}", 1) }
74
+ string = Dragonfly::Serializer.b64_encode(Marshal.dump(c))
75
+ lambda{
76
+ marshal_decode(string)
77
+ }.should raise_error(Dragonfly::Serializer::MaliciousString)
78
+ end
79
+ end
80
+ end
58
81
  end
59
82
 
60
83
  [
@@ -121,6 +121,16 @@ describe Dragonfly::Server do
121
121
  response.headers['X-Cascade'].should be_nil
122
122
  end
123
123
 
124
+ it "should return a 404 when the url is malicious" do
125
+ class C; def initialize; @a = 1; end; end
126
+ url = "/media/#{Dragonfly::Serializer.marshal_encode(C.new)}"
127
+ response = request(@server, url)
128
+ response.status.should == 404
129
+ response.body.should == 'Not found'
130
+ response.content_type.should == 'text/plain'
131
+ response.headers['X-Cascade'].should be_nil
132
+ end
133
+
124
134
  it "should return a 403 Forbidden when someone uses fetch_file" do
125
135
  response = request(@server, "/media/#{@app.fetch_file('/some/file.txt').serialize}")
126
136
  response.status.should == 403
@@ -20,13 +20,13 @@ describe Dragonfly::UrlMapper do
20
20
  url_mapper.params_in_url.should == ['job', 'basename', 'ext']
21
21
  end
22
22
  end
23
-
23
+
24
24
  describe "url_regexp" do
25
25
  it "should return a regexp with non-greedy optional groups that include the preceding slash/dot/dash" do
26
26
  url_mapper = Dragonfly::UrlMapper.new('/media/:job/:basename-:size.:format')
27
27
  url_mapper.url_regexp.should == %r{^/media(/[^\/\-\.]+?)?(/[^\/\-\.]+?)?(\-[^\/\-\.]+?)?(\.[^\/\-\.]+?)?$}
28
28
  end
29
-
29
+
30
30
  it "should allow setting custom patterns in the url" do
31
31
  url_mapper = Dragonfly::UrlMapper.new('/media/:job-:size.:format',
32
32
  :job => '\w',
@@ -35,7 +35,7 @@ describe Dragonfly::UrlMapper do
35
35
  )
36
36
  url_mapper.url_regexp.should == %r{^/media(/\w+?)?(\-\d+?)?(\.[^\.]+?)?$}
37
37
  end
38
-
38
+
39
39
  it "should make optional match patterns (ending in ?) apply to the whole group including the preceding seperator" do
40
40
  url_mapper = Dragonfly::UrlMapper.new('/media/:job', :job => '\w')
41
41
  url_mapper.url_regexp.should == %r{^/media(/\w+?)?$}
@@ -44,28 +44,25 @@ describe Dragonfly::UrlMapper do
44
44
 
45
45
  describe "url_for" do
46
46
  before(:each) do
47
- @url_mapper = Dragonfly::UrlMapper.new('/media/:job-:size',
48
- :job => '\w',
49
- :size => '\w'
50
- )
47
+ @url_mapper = Dragonfly::UrlMapper.new('/media/:job-:size')
51
48
  end
52
-
49
+
53
50
  it "should map correctly" do
54
51
  @url_mapper.url_for('job' => 'asdf', 'size' => '30x30').should == '/media/asdf-30x30'
55
52
  end
56
-
53
+
57
54
  it "should add extra params as query parameters" do
58
55
  @url_mapper.url_for('job' => 'asdf', 'size' => '30x30', 'when' => 'now').should == '/media/asdf-30x30?when=now'
59
56
  end
60
-
57
+
61
58
  it "should not worry if params aren't given" do
62
59
  @url_mapper.url_for('job' => 'asdf', 'when' => 'now', 'then' => 'soon').should match_url '/media/asdf?when=now&then=soon'
63
60
  end
64
-
61
+
65
62
  it "should call to_s on non-string values" do
66
63
  @url_mapper.url_for('job' => 'asdf', 'size' => 500).should == '/media/asdf-500'
67
64
  end
68
-
65
+
69
66
  it "should url-escape funny characters in the path" do
70
67
  @url_mapper.url_for('job' => 'a#c').should == '/media/a%23c'
71
68
  end
@@ -84,20 +81,20 @@ describe Dragonfly::UrlMapper do
84
81
  before(:each) do
85
82
  @url_mapper = Dragonfly::UrlMapper.new('/media/:job')
86
83
  end
87
-
84
+
88
85
  it "should map correctly" do
89
86
  @url_mapper.params_for('/media/asdf').should == {'job' => 'asdf'}
90
87
  end
91
-
88
+
92
89
  it "should include query parameters" do
93
90
  @url_mapper.params_for('/media/asdf', 'when=now').should == {'job' => 'asdf', 'when' => 'now'}
94
91
  end
95
-
92
+
96
93
  it "should generally be ok with wierd characters" do
97
94
  @url_mapper = Dragonfly::UrlMapper.new('/media/:doobie')
98
95
  @url_mapper.params_for('/media/sd sdf jl£@$ sdf:_', 'job=goodun').should == {'job' => 'goodun', 'doobie' => 'sd sdf jl£@$ sdf:_'}
99
96
  end
100
-
97
+
101
98
  it "should correctly url-unescape funny characters" do
102
99
  @url_mapper.params_for('/media/a%23c').should == {'job' => 'a#c'}
103
100
  end
@@ -106,7 +103,6 @@ describe Dragonfly::UrlMapper do
106
103
  describe "matching urls with standard format /media/:job/:basename.:format" do
107
104
  before(:each) do
108
105
  @url_mapper = Dragonfly::UrlMapper.new('/media/:job/:basename.:format',
109
- :job => '\w',
110
106
  :basename => '[^\/]',
111
107
  :format => '[^\.]'
112
108
  )
@@ -126,22 +122,23 @@ describe Dragonfly::UrlMapper do
126
122
  '/media/asdf.egg' => {'job' => 'asdf', 'basename' => nil, 'format' => 'egg'},
127
123
  '/media/asdf/stuff/egg' => nil,
128
124
  '/media/asdf/stuff.dog.egg' => {'job' => 'asdf', 'basename' => 'stuff.dog', 'format' => 'egg'},
129
- '/media/asdf/s%3D2+-.d.e' => {'job' => 'asdf', 'basename' => 's=2 -.d', 'format' => 'e'},
130
- '/media/asdf-40x40/stuff.egg' => nil
125
+ '/media/asdf/s=2+-.d.e' => {'job' => 'asdf', 'basename' => 's=2+-.d', 'format' => 'e'},
126
+ '/media/asdf-40x40/stuff.egg' => nil,
127
+ '/media/a%23c' => {'job' => 'a#c', 'basename' => nil, 'format' => nil}
131
128
  }.each do |path, params|
132
-
129
+
133
130
  it "should turn the url #{path} into params #{params.inspect}" do
134
131
  @url_mapper.params_for(path).should == params
135
- end
136
-
132
+ end
133
+
137
134
  if params
138
135
  it "should turn the params #{params.inspect} into url #{path}" do
139
136
  @url_mapper.url_for(params).should == path
140
137
  end
141
138
  end
142
-
139
+
143
140
  end
144
141
 
145
142
  end
146
-
143
+
147
144
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe "urls" do
4
+
5
+ def request(app, path)
6
+ Rack::MockRequest.new(app).get(path)
7
+ end
8
+
9
+ def job_should_match(array)
10
+ Dragonfly::Response.should_receive(:new).with do |job, env|
11
+ job.to_a.should == array
12
+ end.and_return(mock('response', :to_response => [200, {'Content-Type' => 'text/plain'}, ["OK"]]))
13
+ end
14
+
15
+ let (:app) { test_app }
16
+
17
+ it "works with old marshalled urls (including with tildes in them)" do
18
+ url = "/BAhbBlsHOgZmSSIIPD4~BjoGRVQ"
19
+ job_should_match [["f", "<>?"]]
20
+ response = request(app, url)
21
+ end
22
+
23
+ it "blows up if it detects bad objects" do
24
+ url = "/BAhvOgZDBjoLQHRoaW5nSSIId2VlBjoGRVQ"
25
+ Dragonfly::Response.should_not_receive(:new)
26
+ response = request(app, url)
27
+ response.status.should == 404
28
+ end
29
+
30
+ it "works with the '%2B' character" do
31
+ url = "/W1siZiIsIjIwMTIvMTEvMDMvMTdfMzhfMDhfNTc4X19NR181ODk5Xy5qcGciXSxbInAiLCJ0aHVtYiIsIjQ1MHg0NTA%2BIl1d/_MG_5899+.jpg"
32
+ job_should_match [["f", "2012/11/03/17_38_08_578__MG_5899_.jpg"], ["p", "thumb", "450x450>"]]
33
+ response = request(app, url)
34
+ end
35
+
36
+ it "works when '%2B' has been converted to + (e.g. with nginx)" do
37
+ url = "/W1siZiIsIjIwMTIvMTEvMDMvMTdfMzhfMDhfNTc4X19NR181ODk5Xy5qcGciXSxbInAiLCJ0aHVtYiIsIjQ1MHg0NTA+Il1d/_MG_5899+.jpg"
38
+ job_should_match [["f", "2012/11/03/17_38_08_578__MG_5899_.jpg"], ["p", "thumb", "450x450>"]]
39
+ response = request(app, url)
40
+ end
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dragonfly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.13
4
+ version: 0.9.14
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-30 00:00:00.000000000 Z
12
+ date: 2013-02-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -502,6 +502,7 @@ files:
502
502
  - spec/functional/remote_on_the_fly_spec.rb
503
503
  - spec/functional/shell_commands_spec.rb
504
504
  - spec/functional/to_response_spec.rb
505
+ - spec/functional/urls_spec.rb
505
506
  - spec/spec_helper.rb
506
507
  - spec/support/argument_matchers.rb
507
508
  - spec/support/image_matchers.rb
@@ -529,7 +530,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
529
530
  version: '0'
530
531
  segments:
531
532
  - 0
532
- hash: -2307100787498819433
533
+ hash: -1610166145286138499
533
534
  required_rubygems_version: !ruby/object:Gem::Requirement
534
535
  none: false
535
536
  requirements: