carrierwave 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

data/README.md CHANGED
@@ -4,6 +4,7 @@ This gem provides a simple and extremely flexible way to upload files from Ruby
4
4
  It works well with Rack based web applications, such as Ruby on Rails.
5
5
 
6
6
  [![Build Status](https://secure.travis-ci.org/jnicklas/carrierwave.png)](http://travis-ci.org/jnicklas/carrierwave)
7
+ [![Code Quality](https://codeclimate.com/badge.png)](https://codeclimate.com/github/jnicklas/carrierwave)
7
8
 
8
9
  ## Information
9
10
 
@@ -28,6 +29,8 @@ In Rails, add it to your Gemfile:
28
29
  gem 'carrierwave'
29
30
  ```
30
31
 
32
+ Finally, restart the server to apply the changes.
33
+
31
34
  Note that CarrierWave is not compatible with Rails 2 as of version 0.5. If you want to use
32
35
  Rails 2, please use the 0.4-stable branch on GitHub.
33
36
 
@@ -216,7 +219,7 @@ and cropped to exactly 200 by 200 pixels. The uploader could be used like this:
216
219
  uploader = AvatarUploader.new
217
220
  uploader.store!(my_file) # size: 1024x768
218
221
 
219
- uploader.url # => '/url/to/my_file.png' # size: 800x600
222
+ uploader.url # => '/url/to/my_file.png' # size: 800x800
220
223
  uploader.thumb.url # => '/url/to/thumb_my_file.png' # size: 200x200
221
224
  ```
222
225
 
@@ -297,8 +300,8 @@ of the original version, potentially resulting in faster processing.
297
300
  Often you'll notice that uploaded files disappear when a validation fails.
298
301
  CarrierWave has a feature that makes it easy to remember the uploaded file even
299
302
  in that case. Suppose your `user` model has an uploader mounted on `avatar`
300
- file, just add a hidden field called `avatar_cache`. In Rails, this would look
301
- like this:
303
+ file, just add a hidden field called `avatar_cache` (don't forget to add it to
304
+ the attr_accessible list as necessary). In Rails, this would look like this:
302
305
 
303
306
  ```erb
304
307
  <%= form_for @user, :html => {:multipart => true} do |f| %>
@@ -406,6 +409,7 @@ both globally and on a per-uploader basis:
406
409
  ```ruby
407
410
  CarrierWave.configure do |config|
408
411
  config.permissions = 0666
412
+ config.directory_permissions = 0777
409
413
  config.storage = :file
410
414
  end
411
415
  ```
@@ -507,9 +511,9 @@ CarrierWave.configure do |config|
507
511
  :region => 'eu-west-1' # optional, defaults to 'us-east-1'
508
512
  }
509
513
  config.fog_directory = 'name_of_directory' # required
510
- config.fog_host = 'https://assets.example.com' # optional, defaults to nil
511
514
  config.fog_public = false # optional, defaults to true
512
515
  config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
516
+ config.asset_host = 'https://assets.example.com' # optional, defaults to nil
513
517
  end
514
518
  ```
515
519
 
@@ -550,7 +554,7 @@ This is *highly* recommended, as without it every request requires a lookup
550
554
  of this information.
551
555
 
552
556
  ```ruby
553
- config.fog_host = "http://c000000.cdn.rackspacecloud.com"
557
+ config.asset_host = "http://c000000.cdn.rackspacecloud.com"
554
558
  ```
555
559
 
556
560
  In your uploader, set the storage to :fog
@@ -597,13 +601,13 @@ end
597
601
  That's it! You can still use the `CarrierWave::Uploader#url` method to return
598
602
  the url to the file on Google.
599
603
 
600
- ## Dynamic Fog Host
604
+ ## Dynamic Asset Host
601
605
 
602
- The `fog_host` config property can be assigned a proc (or anything that responds to `call`) for generating the host dynamically. The proc-compliant object gets an instance of the current `CarrierWave::Storage::Fog::File` as its only argument.
606
+ The `asset_host` config property can be assigned a proc (or anything that responds to `call`) for generating the host dynamically. The proc-compliant object gets an instance of the current `CarrierWave::Storage::Fog::File` or `CarrierWave::SanitizedFile` as its only argument.
603
607
 
604
608
  ```ruby
605
609
  CarrierWave.configure do |config|
606
- config.fog_host = proc do |file|
610
+ config.asset_host = proc do |file|
607
611
  identifier = # some logic
608
612
  "http://#{identifier}.cdn.rackspacecloud.com"
609
613
  end
@@ -704,6 +708,7 @@ errors:
704
708
  messages:
705
709
  carrierwave_processing_error: 'Cannot resize image.'
706
710
  carrierwave_integrity_error: 'Not an image.'
711
+ carrierwave_download_error: 'Couldn't download image.'
707
712
  ```
708
713
 
709
714
  ## Large files
@@ -734,7 +739,9 @@ This has only been tested with the local filesystem store.
734
739
  CarrierWave thrives on a large number of [contributors](https://github.com/jnicklas/carrierwave/contributors),
735
740
  and pull requests are very welcome. Before submitting a pull request, please make sure that your changes are well tested.
736
741
 
737
- You'll need to install bundler and the gem dependencies:
742
+ First, make sure you have `imagemagick` and `ghostscript` installed.
743
+
744
+ Then, you'll need to install bundler and the gem dependencies:
738
745
 
739
746
  gem install bundler
740
747
  bundle install
@@ -761,6 +768,11 @@ You should now be able to run the remote tests:
761
768
 
762
769
  Please test with the latest Ruby 1.8.x and 1.9.x versions using RVM if possible.
763
770
 
771
+ ### Running active record tests
772
+
773
+ Make sure you have a local MySQL database named `carrierwave_test` with the username
774
+ `root` and empty password.
775
+
764
776
  ## License
765
777
 
766
778
  Copyright (c) 2008-2012 Jonas Nicklas
@@ -15,8 +15,8 @@ module CarrierWave
15
15
  CarrierWave::Uploader::Base.configure(&block)
16
16
  end
17
17
 
18
- def clean_cached_files!
19
- CarrierWave::Uploader::Base.clean_cached_files!
18
+ def clean_cached_files!(seconds=60*60*24)
19
+ CarrierWave::Uploader::Base.clean_cached_files!(seconds)
20
20
  end
21
21
  end
22
22
 
@@ -50,6 +50,7 @@ module CarrierWave
50
50
  autoload :Versions, 'carrierwave/uploader/versions'
51
51
  autoload :Remove, 'carrierwave/uploader/remove'
52
52
  autoload :ExtensionWhitelist, 'carrierwave/uploader/extension_whitelist'
53
+ autoload :ExtensionBlacklist, 'carrierwave/uploader/extension_blacklist'
53
54
  autoload :DefaultUrl, 'carrierwave/uploader/default_url'
54
55
  autoload :Proxy, 'carrierwave/uploader/proxy'
55
56
  autoload :Url, 'carrierwave/uploader/url'
@@ -95,7 +96,7 @@ elsif defined?(Rails)
95
96
  end
96
97
 
97
98
  elsif defined?(Sinatra)
98
- if defined?(Padrino)
99
+ if defined?(Padrino) && defined?(PADRINO_ROOT)
99
100
  CarrierWave.root = File.join(PADRINO_ROOT, "public")
100
101
  else
101
102
 
@@ -3,7 +3,9 @@ en:
3
3
  messages:
4
4
  carrierwave_processing_error: failed to be processed
5
5
  carrierwave_integrity_error: is not of an allowed file type
6
+ carrierwave_download_error: could not be downloaded
6
7
  extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
+ extension_black_list_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
7
9
  rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image? Original Error: %{e}"
8
10
  mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}"
9
- mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
11
+ mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
@@ -94,6 +94,7 @@ module CarrierWave
94
94
  #
95
95
  # [image_integrity_error] Returns an error object if the last file to be assigned caused an integrity error
96
96
  # [image_processing_error] Returns an error object if the last file to be assigned caused a processing error
97
+ # [image_download_error] Returns an error object if the last file to be remotely assigned caused a download error
97
98
  #
98
99
  # [write_image_identifier] Uses the write_uploader method to set the identifier.
99
100
  # [image_identifier] Reads out the identifier of the file
@@ -225,6 +226,10 @@ module CarrierWave
225
226
  _mounter(:#{column}).processing_error
226
227
  end
227
228
 
229
+ def #{column}_download_error
230
+ _mounter(:#{column}).download_error
231
+ end
232
+
228
233
  def write_#{column}_identifier
229
234
  _mounter(:#{column}).write_identifier
230
235
  end
@@ -281,7 +286,7 @@ module CarrierWave
281
286
  # this is an internal class, used by CarrierWave::Mount so that
282
287
  # we don't pollute the model with a lot of methods.
283
288
  class Mounter #:nodoc:
284
- attr_reader :column, :record, :remote_url, :integrity_error, :processing_error
289
+ attr_reader :column, :record, :remote_url, :integrity_error, :processing_error, :download_error
285
290
  attr_accessor :remove
286
291
 
287
292
  def initialize(record, column, options={})
@@ -291,11 +296,11 @@ module CarrierWave
291
296
  end
292
297
 
293
298
  def write_identifier
294
- if remove?
295
- record.write_uploader(serialization_column, '')
296
- elsif not uploader.identifier.blank?
297
- record.write_uploader(serialization_column, uploader.identifier)
298
- end
299
+ return if record.frozen? || uploader.identifier.blank?
300
+
301
+ value = remove? ? '' : uploader.identifier
302
+
303
+ record.write_uploader(serialization_column, value)
299
304
  end
300
305
 
301
306
  def identifier
@@ -333,8 +338,22 @@ module CarrierWave
333
338
  end
334
339
 
335
340
  def remote_url=(url)
341
+ @download_error = nil
342
+ @integrity_error = nil
343
+
336
344
  @remote_url = url
345
+
337
346
  uploader.download!(url)
347
+
348
+ rescue CarrierWave::DownloadError => e
349
+ @download_error = e
350
+ raise e unless option(:ignore_download_errors)
351
+ rescue CarrierWave::ProcessingError => e
352
+ @processing_error = e
353
+ raise e unless option(:ignore_processing_errors)
354
+ rescue CarrierWave::IntegrityError => e
355
+ @integrity_error = e
356
+ raise e unless option(:ignore_integrity_errors)
338
357
  end
339
358
 
340
359
  def store!
@@ -23,10 +23,11 @@ module CarrierWave
23
23
 
24
24
  validates_integrity_of column if uploader_option(column.to_sym, :validate_integrity)
25
25
  validates_processing_of column if uploader_option(column.to_sym, :validate_processing)
26
+ validates_download_of column if uploader_option(column.to_sym, :validate_download)
26
27
 
27
28
  after_save :"store_#{column}!"
28
29
  before_save :"write_#{column}_identifier"
29
- after_destroy :"remove_#{column}!"
30
+ after_commit :"remove_#{column}!", :on => :destroy
30
31
  before_update :"store_previous_model_for_#{column}"
31
32
  after_save :"remove_previously_stored_#{column}"
32
33
 
@@ -43,6 +44,12 @@ module CarrierWave
43
44
  super
44
45
  end
45
46
 
47
+ def remove_#{column}!
48
+ super
49
+ _mounter(:#{column}).remove = true
50
+ _mounter(:#{column}).write_identifier
51
+ end
52
+
46
53
  def serializable_hash(options=nil)
47
54
  hash = {}
48
55
 
@@ -51,7 +58,7 @@ module CarrierWave
51
58
 
52
59
  self.class.uploaders.each do |column, uploader|
53
60
  if (!only && !except) || (only && only.include?(column.to_s)) || (except && !except.include?(column.to_s))
54
- hash[column.to_s] = _mounter(:#{column}).uploader.serializable_hash
61
+ hash[column.to_s] = _mounter(column).uploader.serializable_hash
55
62
  end
56
63
  end
57
64
  super(options).merge(hash)
@@ -32,6 +32,12 @@ module CarrierWave
32
32
  end
33
33
  end
34
34
 
35
+ GENERIC_CONTENT_TYPES = %w[application/octet-stream binary/octet-stream]
36
+
37
+ def generic_content_type?
38
+ GENERIC_CONTENT_TYPES.include? file.content_type
39
+ end
40
+
35
41
  ##
36
42
  # Changes the file content_type using the mime-types gem
37
43
  #
@@ -42,7 +48,7 @@ module CarrierWave
42
48
  # false by default
43
49
  #
44
50
  def set_content_type(override=false)
45
- if override || file.content_type.blank? || file.content_type == 'application/octet-stream'
51
+ if override || file.content_type.blank? || generic_content_type?
46
52
  new_content_type = ::MIME::Types.type_for(file.original_filename).first.to_s
47
53
  if file.respond_to?(:content_type=)
48
54
  file.content_type = new_content_type
@@ -169,10 +169,17 @@ module CarrierWave
169
169
  cols, rows = img[:dimensions]
170
170
  img.combine_options do |cmd|
171
171
  if width != cols || height != rows
172
- scale = [width/cols.to_f, height/rows.to_f].max
173
- cols = (scale * (cols + 0.5)).round
174
- rows = (scale * (rows + 0.5)).round
175
- cmd.resize "#{cols}x#{rows}"
172
+ scale_x = width/cols.to_f
173
+ scale_y = height/rows.to_f
174
+ if scale_x >= scale_y
175
+ cols = (scale_x * (cols + 0.5)).round
176
+ rows = (scale_x * (rows + 0.5)).round
177
+ cmd.resize "#{cols}"
178
+ else
179
+ cols = (scale_y * (cols + 0.5)).round
180
+ rows = (scale_y * (rows + 0.5)).round
181
+ cmd.resize "x#{rows}"
182
+ end
176
183
  end
177
184
  cmd.gravity gravity
178
185
  cmd.background "rgba(255,255,255,0.0)"
@@ -90,6 +90,10 @@ module CarrierWave
90
90
  def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
91
91
  process :resize_and_pad => [width, height, background, gravity]
92
92
  end
93
+
94
+ def resize_to_geometry_string(geometry_string)
95
+ process :resize_to_geometry_string => [geometry_string]
96
+ end
93
97
  end
94
98
 
95
99
  ##
@@ -223,6 +227,28 @@ module CarrierWave
223
227
  end
224
228
  end
225
229
 
230
+ ##
231
+ # Resize the image per the provided geometry string.
232
+ #
233
+ # === Parameters
234
+ #
235
+ # [geometry_string (String)] the proportions in which to scale image
236
+ #
237
+ # === Yields
238
+ #
239
+ # [Magick::Image] additional manipulations to perform
240
+ #
241
+ def resize_to_geometry_string(geometry_string)
242
+ manipulate! do |img|
243
+ new_img = img.change_geometry(geometry_string) do |new_width, new_height|
244
+ img.resize(new_width, new_height)
245
+ end
246
+ destroy_image(img)
247
+ new_img = yield(new_img) if block_given?
248
+ new_img
249
+ end
250
+ end
251
+
226
252
  ##
227
253
  # Manipulate the image with RMagick. This method will load up an image
228
254
  # and then pass each of its frames to the supplied block. It will then
@@ -238,6 +264,45 @@ module CarrierWave
238
264
  # === Yields
239
265
  #
240
266
  # [Magick::Image] manipulations to perform
267
+ # [Integer] Frame index if the image contains multiple frames
268
+ # [Hash] options, see below
269
+ #
270
+ # === Options
271
+ #
272
+ # The options argument to this method is also yielded as the third
273
+ # block argument.
274
+ #
275
+ # Currently, the following options are defined:
276
+ #
277
+ # ==== :write
278
+ # A hash of assignments to be evaluated in the block given to the RMagick write call.
279
+ #
280
+ # An example:
281
+ #
282
+ # manipulate! do |img, index, options|
283
+ # options[:write] = {
284
+ # :quality => 50,
285
+ # :depth => 8
286
+ # }
287
+ # img
288
+ # end
289
+ #
290
+ # This will translate to the following RMagick::Image#write call:
291
+ #
292
+ # image.write do |img|
293
+ # self.quality = 50
294
+ # self.depth = 8
295
+ # end
296
+ #
297
+ # ==== :read
298
+ # A hash of assignments to be given to the RMagick read call.
299
+ #
300
+ # The options available are identical to those for write, but are passed in directly, like this:
301
+ #
302
+ # manipulate! :read => { :density => 300 }
303
+ #
304
+ # ==== :format
305
+ # Specify the output format. If unset, the filename extension is used to determine the format.
241
306
  #
242
307
  # === Raises
243
308
  #
@@ -245,13 +310,15 @@ module CarrierWave
245
310
  #
246
311
  def manipulate!(options={}, &block)
247
312
  cache_stored_file! if !cached?
248
- image = ::Magick::Image.read(current_path)
313
+
314
+ read_block = create_info_block(options[:read])
315
+ image = ::Magick::Image.read(current_path, &read_block)
249
316
 
250
317
  frames = if image.size > 1
251
318
  list = ::Magick::ImageList.new
252
319
  image.each_with_index do |frame, index|
253
320
  processed_frame = if block_given?
254
- yield *[frame, index].take(block.arity)
321
+ yield *[frame, index, options].take(block.arity)
255
322
  else
256
323
  frame
257
324
  end
@@ -260,14 +327,15 @@ module CarrierWave
260
327
  block_given? ? list : list.append(true)
261
328
  else
262
329
  frame = image.first
263
- frame = yield( *[frame, 0].take(block.arity) ) if block_given?
330
+ frame = yield( *[frame, 0, options].take(block.arity) ) if block_given?
264
331
  frame
265
332
  end
266
333
 
334
+ write_block = create_info_block(options[:write])
267
335
  if options[:format]
268
- frames.write("#{options[:format]}:#{current_path}")
336
+ frames.write("#{options[:format]}:#{current_path}", &write_block)
269
337
  else
270
- frames.write(current_path)
338
+ frames.write(current_path, &write_block)
271
339
  end
272
340
  destroy_image(frames)
273
341
  rescue ::Magick::ImageMagickError => e
@@ -276,6 +344,13 @@ module CarrierWave
276
344
 
277
345
  private
278
346
 
347
+ def create_info_block(options)
348
+ return nil unless options
349
+ assignments = options.map { |k, v| "self.#{k} = #{v}" }
350
+ code = "lambda { |img| " + assignments.join(";") + "}"
351
+ eval code
352
+ end
353
+
279
354
  def destroy_image(image)
280
355
  image.destroy! if image.respond_to?(:destroy!)
281
356
  end
@@ -168,12 +168,13 @@ module CarrierWave
168
168
  #
169
169
  # [new_path (String)] The path where the file should be moved.
170
170
  # [permissions (Integer)] permissions to set on the file in its new location.
171
+ # [directory_permissions (Integer)] permissions to set on created directories.
171
172
  #
172
- def move_to(new_path, permissions=nil)
173
+ def move_to(new_path, permissions=nil, directory_permissions=nil)
173
174
  return if self.empty?
174
175
  new_path = File.expand_path(new_path)
175
176
 
176
- mkdir!(new_path)
177
+ mkdir!(new_path, directory_permissions)
177
178
  if exists?
178
179
  FileUtils.mv(path, new_path) unless new_path == path
179
180
  else
@@ -191,16 +192,17 @@ module CarrierWave
191
192
  #
192
193
  # [new_path (String)] The path where the file should be copied to.
193
194
  # [permissions (Integer)] permissions to set on the copy
195
+ # [directory_permissions (Integer)] permissions to set on created directories.
194
196
  #
195
197
  # === Returns
196
198
  #
197
199
  # @return [CarrierWave::SanitizedFile] the location where the file will be stored.
198
200
  #
199
- def copy_to(new_path, permissions=nil)
201
+ def copy_to(new_path, permissions=nil, directory_permissions=nil)
200
202
  return if self.empty?
201
203
  new_path = File.expand_path(new_path)
202
204
 
203
- mkdir!(new_path)
205
+ mkdir!(new_path, directory_permissions)
204
206
  if exists?
205
207
  FileUtils.cp(path, new_path) unless new_path == path
206
208
  else
@@ -226,7 +228,7 @@ module CarrierWave
226
228
  #
227
229
  def to_file
228
230
  return @file if @file.is_a?(File)
229
- File.open(path) if exists?
231
+ File.open(path, "rb") if exists?
230
232
  end
231
233
 
232
234
  ##
@@ -278,8 +280,10 @@ module CarrierWave
278
280
  end
279
281
 
280
282
  # create the directory if it doesn't exist
281
- def mkdir!(path)
282
- FileUtils.mkdir_p(File.dirname(path)) unless File.exists?(File.dirname(path))
283
+ def mkdir!(path, directory_permissions)
284
+ options = {}
285
+ options[:mode] = directory_permissions if directory_permissions
286
+ FileUtils.mkdir_p(File.dirname(path), options) unless File.exists?(File.dirname(path))
283
287
  end
284
288
 
285
289
  def chmod!(path, permissions)
@@ -299,7 +303,7 @@ module CarrierWave
299
303
  def split_extension(filename)
300
304
  # regular expressions to try for identifying extensions
301
305
  extension_matchers = [
302
- /\A(.+)\.(tar\.gz)\z/, # matches "something.tar.gz"
306
+ /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz"
303
307
  /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
304
308
  ]
305
309
 
@@ -29,9 +29,9 @@ module CarrierWave
29
29
  def store!(file)
30
30
  path = ::File.expand_path(uploader.store_path, uploader.root)
31
31
  if uploader.move_to_store
32
- file.move_to(path, uploader.permissions)
32
+ file.move_to(path, uploader.permissions, uploader.directory_permissions)
33
33
  else
34
- file.copy_to(path, uploader.permissions)
34
+ file.copy_to(path, uploader.permissions, uploader.directory_permissions)
35
35
  end
36
36
  end
37
37
 
@@ -20,7 +20,7 @@ module CarrierWave
20
20
  # [:fog_directory] specifies name of directory to store data in, assumed to already exist
21
21
  #
22
22
  # [:fog_attributes] (optional) additional attributes to set on files
23
- # [:fog_host] (optional) non-default host to serve files from
23
+ # [:fog_endpoint] (optional) non-default host to connect with
24
24
  # [:fog_public] (optional) public readability, defaults to true
25
25
  # [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
26
26
  # will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
@@ -102,8 +102,16 @@ module CarrierWave
102
102
 
103
103
  def connection
104
104
  @connection ||= begin
105
- credentials = uploader.fog_credentials
106
- self.class.connection_cache[credentials] ||= ::Fog::Storage.new(credentials)
105
+ options = credentials = uploader.fog_credentials
106
+ endpoint_url = if uploader.fog_endpoint.respond_to? :call
107
+ URI.parse( uploader.fog_endpoint.call(self) )
108
+ elsif uploader.fog_endpoint
109
+ URI.parse( uploader.fog_endpoint )
110
+ end
111
+ if host_string = endpoint_url && (endpoint_url.host || endpoint_url.to_s)
112
+ options.merge!( { :host => host_string } )
113
+ end
114
+ self.class.connection_cache[credentials] ||= ::Fog::Storage.new(options)
107
115
  end
108
116
  end
109
117
 
@@ -267,7 +275,7 @@ module CarrierWave
267
275
  # [NilClass] no public url available
268
276
  #
269
277
  def public_url
270
- if host = @uploader.fog_host
278
+ if host = @uploader.asset_host
271
279
  if host.respond_to? :call
272
280
  "#{host.call(self)}/#{path}"
273
281
  else
@@ -278,7 +286,7 @@ module CarrierWave
278
286
  case @uploader.fog_credentials[:provider]
279
287
  when 'AWS'
280
288
  # if directory is a valid subdomain, use that style for access
281
- if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
289
+ if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
282
290
  "https://#{@uploader.fog_directory}.s3.amazonaws.com/#{path}"
283
291
  else
284
292
  # directory is not a valid subdomain, so use path style for access
@@ -310,6 +318,21 @@ module CarrierWave
310
318
  end
311
319
  end
312
320
 
321
+ ##
322
+ # Return file name, if available
323
+ #
324
+ # === Returns
325
+ #
326
+ # [String] file name
327
+ # or
328
+ # [NilClass] no file name available
329
+ #
330
+ def filename(options = {})
331
+ if file_url = url(options)
332
+ file_url.gsub(/.*\/(.*?$)/, '\1')
333
+ end
334
+ end
335
+
313
336
  private
314
337
 
315
338
  ##
@@ -64,6 +64,34 @@ module CarrierWave
64
64
  HavePermissions.new(expected)
65
65
  end
66
66
 
67
+ class HaveDirectoryPermissions # :nodoc:
68
+ def initialize(expected)
69
+ @expected = expected
70
+ end
71
+
72
+ def matches?(actual)
73
+ @actual = actual
74
+ # Satisfy expectation here. Return false or raise an error if it's not met.
75
+ (File.stat(File.dirname @actual.path).mode & 0777) == @expected
76
+ end
77
+
78
+ def failure_message
79
+ "expected #{File.dirname @actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
80
+ end
81
+
82
+ def negative_failure_message
83
+ "expected #{File.dirname @actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
84
+ end
85
+
86
+ def description
87
+ "have permissions #{@expected.to_s(8)}"
88
+ end
89
+ end
90
+
91
+ def have_directory_permissions(expected)
92
+ HaveDirectoryPermissions.new(expected)
93
+ end
94
+
67
95
  class BeNoLargerThan # :nodoc:
68
96
  def initialize(width, height)
69
97
  @width, @height = width, height
@@ -126,6 +154,66 @@ module CarrierWave
126
154
  HaveDimensions.new(width, height)
127
155
  end
128
156
 
157
+ class HaveHeight # :nodoc:
158
+ def initialize(height)
159
+ @height = height
160
+ end
161
+
162
+ def matches?(actual)
163
+ @actual = actual
164
+ # Satisfy expectation here. Return false or raise an error if it's not met.
165
+ image = ImageLoader.load_image(@actual.current_path)
166
+ @actual_height = image.height
167
+ @actual_height == @height
168
+ end
169
+
170
+ def failure_message
171
+ "expected #{@actual.current_path.inspect} to have an exact size of #{@height}, but it was #{@actual_height}."
172
+ end
173
+
174
+ def negative_failure_message
175
+ "expected #{@actual.current_path.inspect} not to have an exact size of #{@height}, but it did."
176
+ end
177
+
178
+ def description
179
+ "have an exact height of #{@height}"
180
+ end
181
+ end
182
+
183
+ def have_height(height)
184
+ HaveHeight.new(height)
185
+ end
186
+
187
+ class HaveWidth # :nodoc:
188
+ def initialize(width)
189
+ @width = width
190
+ end
191
+
192
+ def matches?(actual)
193
+ @actual = actual
194
+ # Satisfy expectation here. Return false or raise an error if it's not met.
195
+ image = ImageLoader.load_image(@actual.current_path)
196
+ @actual_width = image.width
197
+ @actual_width == @width
198
+ end
199
+
200
+ def failure_message
201
+ "expected #{@actual.current_path.inspect} to have an exact size of #{@width}, but it was #{@actual_width}."
202
+ end
203
+
204
+ def negative_failure_message
205
+ "expected #{@actual.current_path.inspect} not to have an exact size of #{@width}, but it did."
206
+ end
207
+
208
+ def description
209
+ "have an exact width of #{@width}"
210
+ end
211
+ end
212
+
213
+ def have_width(width)
214
+ HaveWidth.new(width)
215
+ end
216
+
129
217
  class BeNoWiderThan # :nodoc:
130
218
  def initialize(width)
131
219
  @width = width
@@ -34,6 +34,7 @@ module CarrierWave
34
34
  include CarrierWave::Uploader::Download
35
35
  include CarrierWave::Uploader::Remove
36
36
  include CarrierWave::Uploader::ExtensionWhitelist
37
+ include CarrierWave::Uploader::ExtensionBlacklist
37
38
  include CarrierWave::Uploader::Processing
38
39
  include CarrierWave::Uploader::Versions
39
40
  include CarrierWave::Uploader::DefaultUrl
@@ -70,8 +70,15 @@ module CarrierWave
70
70
  # require the file to be stored on the local filesystem.
71
71
  #
72
72
  def cache_stored_file!
73
- sanitized = SanitizedFile.new :tempfile => StringIO.new(file.read),
74
- :filename => File.basename(path), :content_type => file.content_type
73
+ _content = file.read
74
+ if _content.is_a?(File) # could be if storage is Fog
75
+ sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path))
76
+ sanitized.read if sanitized.exists?
77
+
78
+ else
79
+ sanitized = SanitizedFile.new :tempfile => StringIO.new(file.read),
80
+ :filename => File.basename(path), :content_type => file.content_type
81
+ end
75
82
 
76
83
  cache! sanitized
77
84
  end
@@ -116,9 +123,9 @@ module CarrierWave
116
123
  self.original_filename = new_file.filename
117
124
 
118
125
  if move_to_cache
119
- @file = new_file.move_to(cache_path, permissions)
126
+ @file = new_file.move_to(cache_path, permissions, directory_permissions)
120
127
  else
121
- @file = new_file.copy_to(cache_path, permissions)
128
+ @file = new_file.copy_to(cache_path, permissions, directory_permissions)
122
129
  end
123
130
  end
124
131
  end
@@ -9,7 +9,9 @@ module CarrierWave
9
9
 
10
10
  add_config :root
11
11
  add_config :base_path
12
+ add_config :asset_host
12
13
  add_config :permissions
14
+ add_config :directory_permissions
13
15
  add_config :storage_engines
14
16
  add_config :store_dir
15
17
  add_config :cache_dir
@@ -24,15 +26,17 @@ module CarrierWave
24
26
  add_config :fog_attributes
25
27
  add_config :fog_credentials
26
28
  add_config :fog_directory
27
- add_config :fog_host
29
+ add_config :fog_endpoint
28
30
  add_config :fog_public
29
31
  add_config :fog_authenticated_url_expiration
30
32
 
31
33
  # Mounting
32
34
  add_config :ignore_integrity_errors
33
35
  add_config :ignore_processing_errors
36
+ add_config :ignore_download_errors
34
37
  add_config :validate_integrity
35
38
  add_config :validate_processing
39
+ add_config :validate_download
36
40
  add_config :mount_on
37
41
 
38
42
  # set default values
@@ -103,6 +107,7 @@ module CarrierWave
103
107
  def reset_config
104
108
  configure do |config|
105
109
  config.permissions = 0644
110
+ config.directory_permissions = 0755
106
111
  config.storage_engines = {
107
112
  :file => "CarrierWave::Storage::File",
108
113
  :fog => "CarrierWave::Storage::Fog"
@@ -120,8 +125,10 @@ module CarrierWave
120
125
  config.remove_previously_stored_files_after_update = true
121
126
  config.ignore_integrity_errors = true
122
127
  config.ignore_processing_errors = true
128
+ config.ignore_download_errors = true
123
129
  config.validate_integrity = true
124
130
  config.validate_processing = true
131
+ config.validate_download = true
125
132
  config.root = lambda { CarrierWave.root }
126
133
  config.base_path = CarrierWave.base_path
127
134
  config.enable_processing = true
@@ -36,6 +36,9 @@ module CarrierWave
36
36
  @file = @file.is_a?(String) ? StringIO.new(@file) : @file
37
37
  end
38
38
  @file
39
+
40
+ rescue
41
+ raise CarrierWave::DownloadError, "could not download file"
39
42
  end
40
43
 
41
44
  def method_missing(*args, &block)
@@ -67,7 +70,7 @@ module CarrierWave
67
70
  # [url (String)] The URL where the remote file is stored
68
71
  #
69
72
  def process_uri(uri)
70
- URI.parse(URI.escape(URI.unescape(uri)).gsub("[", "%5B").gsub("]", "%5D").gsub("+", "%2B"))
73
+ URI.parse(URI.escape(URI.unescape(uri)))
71
74
  end
72
75
 
73
76
  end # Download
@@ -0,0 +1,47 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ExtensionBlacklist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_blacklist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide a black list of extensions which
12
+ # are prohibited to be uploaded. Compares the file's extension case insensitive.
13
+ # Furthermore, not only strings but Regexp are allowed as well.
14
+ #
15
+ # When using a Regexp in the black list, `\A` and `\z` are automatically added to
16
+ # the Regexp expression, also case insensitive.
17
+ #
18
+ # === Returns
19
+
20
+ # [NilClass, Array[String,Regexp]] a black list of extensions which are prohibited to be uploaded
21
+ #
22
+ # === Examples
23
+ #
24
+ # def extension_black_list
25
+ # %w(swf tiff)
26
+ # end
27
+ #
28
+ # Basically the same, but using a Regexp:
29
+ #
30
+ # def extension_black_list
31
+ # [/swf/, 'tiff']
32
+ # end
33
+ #
34
+
35
+ def extension_black_list; end
36
+
37
+ private
38
+
39
+ def check_blacklist!(new_file)
40
+ extension = new_file.extension.to_s
41
+ if extension_black_list and extension_black_list.detect { |item| extension =~ /\A#{item}\z/i }
42
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_black_list_error", :extension => new_file.extension.inspect, :prohibited_types => extension_black_list.join(", "))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -40,7 +40,7 @@ module CarrierWave
40
40
  def check_whitelist!(new_file)
41
41
  extension = new_file.extension.to_s
42
42
  if extension_white_list and not extension_white_list.detect { |item| extension =~ /\A#{item}\z/i }
43
- raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_white_list_error", :extension => new_file.extension.inspect, :allowed_types => extension_white_list.inspect)
43
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_white_list_error", :extension => new_file.extension.inspect, :allowed_types => extension_white_list.join(", "))
44
44
  end
45
45
  end
46
46
 
@@ -18,8 +18,18 @@ module CarrierWave
18
18
  def url(options = {})
19
19
  if file.respond_to?(:url) and not file.url.blank?
20
20
  file.method(:url).arity == 0 ? file.url : file.url(options)
21
- elsif current_path
22
- (base_path || "") + File.expand_path(current_path).gsub(File.expand_path(root), '')
21
+ elsif file.respond_to?(:path)
22
+ path = file.path.gsub(File.expand_path(root), '')
23
+
24
+ if host = asset_host
25
+ if host.respond_to? :call
26
+ "#{host.call(file)}#{path}"
27
+ else
28
+ "#{host}#{path}"
29
+ end
30
+ else
31
+ (base_path || "") + path
32
+ end
23
33
  end
24
34
  end
25
35
 
@@ -30,6 +30,16 @@ module CarrierWave
30
30
  end
31
31
  end
32
32
 
33
+ class DownloadValidator < ::ActiveModel::EachValidator
34
+
35
+ def validate_each(record, attribute, value)
36
+ if e = record.send("#{attribute}_download_error")
37
+ message = (e.message == e.class.to_s) ? :carrierwave_download_error : e.message
38
+ record.errors.add(attribute, message)
39
+ end
40
+ end
41
+ end
42
+
33
43
  module HelperMethods
34
44
 
35
45
  ##
@@ -50,6 +60,15 @@ module CarrierWave
50
60
  def validates_processing_of(*attr_names)
51
61
  validates_with ProcessingValidator, _merge_attributes(attr_names)
52
62
  end
63
+ #
64
+ ##
65
+ # Makes the record invalid if the remote file couldn't be downloaded
66
+ #
67
+ # Accepts the usual parameters for validations in Rails (:if, :unless, etc...)
68
+ #
69
+ def validates_download_of(*attr_names)
70
+ validates_with DownloadValidator, _merge_attributes(attr_names)
71
+ end
53
72
  end
54
73
 
55
74
  included do
@@ -1,3 +1,3 @@
1
1
  module CarrierWave
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carrierwave
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-12 00:00:00.000000000 Z
12
+ date: 2012-10-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70103427625320 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 3.2.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70103427625320
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.0
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: activemodel
27
- requirement: &70103427624520 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 3.2.0
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *70103427624520
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.2.0
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: mysql2
38
- requirement: &70103427623860 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70103427623860
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: rails
49
- requirement: &70103427623000 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,21 +69,31 @@ dependencies:
54
69
  version: 3.2.0
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *70103427623000
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 3.2.0
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: cucumber
60
- requirement: &70103427622140 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
- - - =
83
+ - - ~>
64
84
  - !ruby/object:Gem::Version
65
85
  version: 1.1.4
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *70103427622140
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.1.4
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: json
71
- requirement: &70103427621460 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
72
97
  none: false
73
98
  requirements:
74
99
  - - ! '>='
@@ -76,21 +101,31 @@ dependencies:
76
101
  version: '0'
77
102
  type: :development
78
103
  prerelease: false
79
- version_requirements: *70103427621460
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
80
110
  - !ruby/object:Gem::Dependency
81
111
  name: rspec
82
- requirement: &70103427620560 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
83
113
  none: false
84
114
  requirements:
85
115
  - - ~>
86
116
  - !ruby/object:Gem::Version
87
- version: '2.0'
117
+ version: 2.10.0
88
118
  type: :development
89
119
  prerelease: false
90
- version_requirements: *70103427620560
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 2.10.0
91
126
  - !ruby/object:Gem::Dependency
92
127
  name: sham_rack
93
- requirement: &70103427620060 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
94
129
  none: false
95
130
  requirements:
96
131
  - - ! '>='
@@ -98,10 +133,15 @@ dependencies:
98
133
  version: '0'
99
134
  type: :development
100
135
  prerelease: false
101
- version_requirements: *70103427620060
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
102
142
  - !ruby/object:Gem::Dependency
103
143
  name: timecop
104
- requirement: &70103427619580 !ruby/object:Gem::Requirement
144
+ requirement: !ruby/object:Gem::Requirement
105
145
  none: false
106
146
  requirements:
107
147
  - - ! '>='
@@ -109,10 +149,15 @@ dependencies:
109
149
  version: '0'
110
150
  type: :development
111
151
  prerelease: false
112
- version_requirements: *70103427619580
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
113
158
  - !ruby/object:Gem::Dependency
114
159
  name: fog
115
- requirement: &70103427619000 !ruby/object:Gem::Requirement
160
+ requirement: !ruby/object:Gem::Requirement
116
161
  none: false
117
162
  requirements:
118
163
  - - ! '>='
@@ -120,10 +165,15 @@ dependencies:
120
165
  version: 1.3.1
121
166
  type: :development
122
167
  prerelease: false
123
- version_requirements: *70103427619000
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: 1.3.1
124
174
  - !ruby/object:Gem::Dependency
125
175
  name: mini_magick
126
- requirement: &70103427618580 !ruby/object:Gem::Requirement
176
+ requirement: !ruby/object:Gem::Requirement
127
177
  none: false
128
178
  requirements:
129
179
  - - ! '>='
@@ -131,10 +181,15 @@ dependencies:
131
181
  version: '0'
132
182
  type: :development
133
183
  prerelease: false
134
- version_requirements: *70103427618580
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
135
190
  - !ruby/object:Gem::Dependency
136
191
  name: rmagick
137
- requirement: &70103427618120 !ruby/object:Gem::Requirement
192
+ requirement: !ruby/object:Gem::Requirement
138
193
  none: false
139
194
  requirements:
140
195
  - - ! '>='
@@ -142,7 +197,12 @@ dependencies:
142
197
  version: '0'
143
198
  type: :development
144
199
  prerelease: false
145
- version_requirements: *70103427618120
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
146
206
  description: Upload files in your Ruby applications, map them to a range of ORMs,
147
207
  store them on different backends.
148
208
  email:
@@ -169,6 +229,7 @@ files:
169
229
  - lib/carrierwave/uploader/configuration.rb
170
230
  - lib/carrierwave/uploader/default_url.rb
171
231
  - lib/carrierwave/uploader/download.rb
232
+ - lib/carrierwave/uploader/extension_blacklist.rb
172
233
  - lib/carrierwave/uploader/extension_whitelist.rb
173
234
  - lib/carrierwave/uploader/mountable.rb
174
235
  - lib/carrierwave/uploader/processing.rb
@@ -206,7 +267,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
267
  version: '0'
207
268
  requirements: []
208
269
  rubyforge_project: carrierwave
209
- rubygems_version: 1.8.10
270
+ rubygems_version: 1.8.23
210
271
  signing_key:
211
272
  specification_version: 3
212
273
  summary: Ruby file upload library