mini-smart-pkg 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/carrierwave-3.1.3/README.md +1214 -0
  3. data/carrierwave-3.1.3/lib/carrierwave/compatibility/paperclip.rb +105 -0
  4. data/carrierwave-3.1.3/lib/carrierwave/downloader/base.rb +101 -0
  5. data/carrierwave-3.1.3/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/carrierwave-3.1.3/lib/carrierwave/error.rb +8 -0
  7. data/carrierwave-3.1.3/lib/carrierwave/locale/en.yml +17 -0
  8. data/carrierwave-3.1.3/lib/carrierwave/mount.rb +446 -0
  9. data/carrierwave-3.1.3/lib/carrierwave/mounter.rb +257 -0
  10. data/carrierwave-3.1.3/lib/carrierwave/orm/activerecord.rb +68 -0
  11. data/carrierwave-3.1.3/lib/carrierwave/processing/mini_magick.rb +362 -0
  12. data/carrierwave-3.1.3/lib/carrierwave/processing/rmagick.rb +433 -0
  13. data/carrierwave-3.1.3/lib/carrierwave/processing/vips.rb +315 -0
  14. data/carrierwave-3.1.3/lib/carrierwave/processing.rb +3 -0
  15. data/carrierwave-3.1.3/lib/carrierwave/sanitized_file.rb +361 -0
  16. data/carrierwave-3.1.3/lib/carrierwave/storage/abstract.rb +43 -0
  17. data/carrierwave-3.1.3/lib/carrierwave/storage/file.rb +124 -0
  18. data/carrierwave-3.1.3/lib/carrierwave/storage/fog.rb +560 -0
  19. data/carrierwave-3.1.3/lib/carrierwave/storage.rb +3 -0
  20. data/carrierwave-3.1.3/lib/carrierwave/test/matchers.rb +398 -0
  21. data/carrierwave-3.1.3/lib/carrierwave/uploader/cache.rb +223 -0
  22. data/carrierwave-3.1.3/lib/carrierwave/uploader/callbacks.rb +33 -0
  23. data/carrierwave-3.1.3/lib/carrierwave/uploader/configuration.rb +229 -0
  24. data/carrierwave-3.1.3/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  25. data/carrierwave-3.1.3/lib/carrierwave/uploader/content_type_denylist.rb +65 -0
  26. data/carrierwave-3.1.3/lib/carrierwave/uploader/default_url.rb +17 -0
  27. data/carrierwave-3.1.3/lib/carrierwave/uploader/dimension.rb +66 -0
  28. data/carrierwave-3.1.3/lib/carrierwave/uploader/download.rb +24 -0
  29. data/carrierwave-3.1.3/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  30. data/carrierwave-3.1.3/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  31. data/carrierwave-3.1.3/lib/carrierwave/uploader/file_size.rb +43 -0
  32. data/carrierwave-3.1.3/lib/carrierwave/uploader/mountable.rb +44 -0
  33. data/carrierwave-3.1.3/lib/carrierwave/uploader/processing.rb +128 -0
  34. data/carrierwave-3.1.3/lib/carrierwave/uploader/proxy.rb +99 -0
  35. data/carrierwave-3.1.3/lib/carrierwave/uploader/remove.rb +21 -0
  36. data/carrierwave-3.1.3/lib/carrierwave/uploader/serialization.rb +28 -0
  37. data/carrierwave-3.1.3/lib/carrierwave/uploader/store.rb +168 -0
  38. data/carrierwave-3.1.3/lib/carrierwave/uploader/url.rb +44 -0
  39. data/carrierwave-3.1.3/lib/carrierwave/uploader/versions.rb +352 -0
  40. data/carrierwave-3.1.3/lib/carrierwave/uploader.rb +69 -0
  41. data/carrierwave-3.1.3/lib/carrierwave/utilities/file_name.rb +47 -0
  42. data/carrierwave-3.1.3/lib/carrierwave/utilities/uri.rb +26 -0
  43. data/carrierwave-3.1.3/lib/carrierwave/utilities.rb +7 -0
  44. data/carrierwave-3.1.3/lib/carrierwave/validations/active_model.rb +76 -0
  45. data/carrierwave-3.1.3/lib/carrierwave/version.rb +3 -0
  46. data/carrierwave-3.1.3/lib/carrierwave.rb +108 -0
  47. data/carrierwave-3.1.3/lib/generators/templates/uploader.rb.erb +56 -0
  48. data/carrierwave-3.1.3/lib/generators/uploader_generator.rb +7 -0
  49. data/mini-smart-pkg.gemspec +12 -0
  50. metadata +89 -0
@@ -0,0 +1,315 @@
1
+ module CarrierWave
2
+
3
+ ##
4
+ # This module simplifies manipulation with vips by providing a set
5
+ # of convenient helper methods. If you want to use them, you'll need to
6
+ # require this file:
7
+ #
8
+ # require 'carrierwave/processing/vips'
9
+ #
10
+ # And then include it in your uploader:
11
+ #
12
+ # class MyUploader < CarrierWave::Uploader::Base
13
+ # include CarrierWave::Vips
14
+ # end
15
+ #
16
+ # You can now use the provided helpers:
17
+ #
18
+ # class MyUploader < CarrierWave::Uploader::Base
19
+ # include CarrierWave::Vips
20
+ #
21
+ # process :resize_to_fit => [200, 200]
22
+ # end
23
+ #
24
+ # Or create your own helpers with the powerful vips! method, which
25
+ # yields an ImageProcessing::Builder object. Check out the ImageProcessing
26
+ # docs at http://github.com/janko-m/image_processing and the list of all
27
+ # available Vips options at
28
+ # https://libvips.github.io/libvips/API/current/using-cli.html for more info.
29
+ #
30
+ # class MyUploader < CarrierWave::Uploader::Base
31
+ # include CarrierWave::Vips
32
+ #
33
+ # process :radial_blur => 10
34
+ #
35
+ # def radial_blur(amount)
36
+ # vips! do |builder|
37
+ # builder.radial_blur(amount)
38
+ # builder = yield(builder) if block_given?
39
+ # builder
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # === Note
45
+ #
46
+ # The ImageProcessing gem uses ruby-vips, a binding for the vips image
47
+ # library. You can find more information here:
48
+ #
49
+ # https://github.com/libvips/ruby-vips
50
+ #
51
+ #
52
+ module Vips
53
+ extend ActiveSupport::Concern
54
+
55
+ included do
56
+ require "image_processing/vips"
57
+ # We need to disable caching since we're editing images in place.
58
+ ::Vips.cache_set_max(0)
59
+ end
60
+
61
+ module ClassMethods
62
+ def convert(format)
63
+ process :convert => format
64
+ end
65
+
66
+ def resize_to_limit(width, height)
67
+ process :resize_to_limit => [width, height]
68
+ end
69
+
70
+ def resize_to_fit(width, height)
71
+ process :resize_to_fit => [width, height]
72
+ end
73
+
74
+ def resize_to_fill(width, height, gravity='centre')
75
+ process :resize_to_fill => [width, height, gravity]
76
+ end
77
+
78
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
79
+ process :resize_and_pad => [width, height, background, gravity, alpha]
80
+ end
81
+
82
+ def crop(left, top, width, height)
83
+ process :crop => [left, top, width, height]
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Changes the image encoding format to the given format
89
+ #
90
+ # See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
91
+ #
92
+ # === Parameters
93
+ #
94
+ # [format (#to_s)] an abbreviation of the format
95
+ #
96
+ # === Yields
97
+ #
98
+ # [Vips::Image] additional manipulations to perform
99
+ #
100
+ # === Examples
101
+ #
102
+ # image.convert(:png)
103
+ #
104
+ def convert(format, page=nil)
105
+ vips! do |builder|
106
+ builder = builder.convert(format)
107
+ builder = builder.loader(page: page) if page
108
+ builder
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Resize the image to fit within the specified dimensions while retaining
114
+ # the original aspect ratio. Will only resize the image if it is larger than the
115
+ # specified dimensions. The resulting image may be shorter or narrower than specified
116
+ # in the smaller dimension but will not be larger than the specified values.
117
+ #
118
+ # === Parameters
119
+ #
120
+ # [width (Integer)] the width to scale the image to
121
+ # [height (Integer)] the height to scale the image to
122
+ # [combine_options (Hash)] additional Vips options to apply before resizing
123
+ #
124
+ # === Yields
125
+ #
126
+ # [Vips::Image] additional manipulations to perform
127
+ #
128
+ def resize_to_limit(width, height, combine_options: {})
129
+ width, height = resolve_dimensions(width, height)
130
+
131
+ vips! do |builder|
132
+ builder.resize_to_limit(width, height)
133
+ .apply(combine_options)
134
+ end
135
+ end
136
+
137
+ ##
138
+ # Resize the image to fit within the specified dimensions while retaining
139
+ # the original aspect ratio. The image may be shorter or narrower than
140
+ # specified in the smaller dimension but will not be larger than the specified values.
141
+ #
142
+ # === Parameters
143
+ #
144
+ # [width (Integer)] the width to scale the image to
145
+ # [height (Integer)] the height to scale the image to
146
+ # [combine_options (Hash)] additional Vips options to apply before resizing
147
+ #
148
+ # === Yields
149
+ #
150
+ # [Vips::Image] additional manipulations to perform
151
+ #
152
+ def resize_to_fit(width, height, combine_options: {})
153
+ width, height = resolve_dimensions(width, height)
154
+
155
+ vips! do |builder|
156
+ builder.resize_to_fit(width, height)
157
+ .apply(combine_options)
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Resize the image to fit within the specified dimensions while retaining
163
+ # the aspect ratio of the original image. If necessary, crop the image in the
164
+ # larger dimension.
165
+ #
166
+ # === Parameters
167
+ #
168
+ # [width (Integer)] the width to scale the image to
169
+ # [height (Integer)] the height to scale the image to
170
+ # [combine_options (Hash)] additional vips options to apply before resizing
171
+ #
172
+ # === Yields
173
+ #
174
+ # [Vips::Image] additional manipulations to perform
175
+ #
176
+ def resize_to_fill(width, height, _gravity = nil, combine_options: {})
177
+ width, height = resolve_dimensions(width, height)
178
+
179
+ vips! do |builder|
180
+ builder.resize_to_fill(width, height).apply(combine_options)
181
+ end
182
+ end
183
+
184
+ ##
185
+ # Resize the image to fit within the specified dimensions while retaining
186
+ # the original aspect ratio. If necessary, will pad the remaining area
187
+ # with the given color, which defaults to transparent (for gif and png,
188
+ # white for jpeg).
189
+ #
190
+ # See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
191
+ # for gravity options.
192
+ #
193
+ # === Parameters
194
+ #
195
+ # [width (Integer)] the width to scale the image to
196
+ # [height (Integer)] the height to scale the image to
197
+ # [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
198
+ # [gravity (String)] how to position the image
199
+ # [alpha (Boolean, nil)] pad the image with the alpha channel if supported
200
+ # [combine_options (Hash)] additional vips options to apply before resizing
201
+ #
202
+ # === Yields
203
+ #
204
+ # [Vips::Image] additional manipulations to perform
205
+ #
206
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
207
+ width, height = resolve_dimensions(width, height)
208
+
209
+ vips! do |builder|
210
+ builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
211
+ .apply(combine_options)
212
+ end
213
+ end
214
+
215
+ ##
216
+ # Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
217
+ # by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
218
+ # outside the image bounds.
219
+ #
220
+ # === Parameters
221
+ #
222
+ # [left (integer)] left edge of area to extract
223
+ # [top (integer)] top edge of area to extract
224
+ # [width (Integer)] width of area to extract
225
+ # [height (Integer)] height of area to extract
226
+ #
227
+ # === Yields
228
+ #
229
+ # [Vips::Image] additional manipulations to perform
230
+ #
231
+ def crop(left, top, width, height, combine_options: {})
232
+ width, height = resolve_dimensions(width, height)
233
+ width = vips_image.width - left if width + left > vips_image.width
234
+ height = vips_image.height - top if height + top > vips_image.height
235
+
236
+ vips! do |builder|
237
+ builder.crop(left, top, width, height)
238
+ .apply(combine_options)
239
+ end
240
+ end
241
+
242
+ ##
243
+ # Returns the width of the image in pixels.
244
+ #
245
+ # === Returns
246
+ #
247
+ # [Integer] the image's width in pixels
248
+ #
249
+ def width
250
+ vips_image.width
251
+ end
252
+
253
+ ##
254
+ # Returns the height of the image in pixels.
255
+ #
256
+ # === Returns
257
+ #
258
+ # [Integer] the image's height in pixels
259
+ #
260
+ def height
261
+ vips_image.height
262
+ end
263
+
264
+ # Process the image with vip, using the ImageProcessing gem. This
265
+ # method will build a "convert" vips command and execute it on the
266
+ # current image.
267
+ #
268
+ # === Gotcha
269
+ #
270
+ # This method assumes that the object responds to +current_path+.
271
+ # Any class that this module is mixed into must have a +current_path+ method.
272
+ # CarrierWave::Uploader does, so you won't need to worry about this in
273
+ # most cases.
274
+ #
275
+ # === Yields
276
+ #
277
+ # [ImageProcessing::Builder] use it to define processing to be performed
278
+ #
279
+ # === Raises
280
+ #
281
+ # [CarrierWave::ProcessingError] if processing failed.
282
+ def vips!
283
+ builder = ImageProcessing::Vips.source(current_path)
284
+ builder = yield(builder)
285
+
286
+ result = builder.call
287
+ result.close
288
+
289
+ FileUtils.mv result.path, current_path
290
+
291
+ if File.extname(result.path) != File.extname(current_path)
292
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
293
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
294
+ file.move_to(move_to, permissions, directory_permissions)
295
+ end
296
+ rescue ::Vips::Error
297
+ message = I18n.translate(:"errors.messages.processing_error")
298
+ raise CarrierWave::ProcessingError, message
299
+ end
300
+
301
+ private
302
+
303
+ def resolve_dimensions(*dimensions)
304
+ dimensions.map do |value|
305
+ next value unless value.instance_of?(Proc)
306
+ value.arity >= 1 ? value.call(self) : value.call
307
+ end
308
+ end
309
+
310
+ def vips_image
311
+ ::Vips::Image.new_from_buffer(read, "")
312
+ end
313
+
314
+ end # Vips
315
+ end # CarrierWave
@@ -0,0 +1,3 @@
1
+ require "carrierwave/processing/rmagick"
2
+ require "carrierwave/processing/mini_magick"
3
+ require "carrierwave/processing/vips"
@@ -0,0 +1,361 @@
1
+ require 'pathname'
2
+ require 'marcel'
3
+
4
+ module CarrierWave
5
+
6
+ ##
7
+ # SanitizedFile is a base class which provides a common API around all
8
+ # the different quirky Ruby File libraries. It has support for Tempfile,
9
+ # File, StringIO, Merb-style upload Hashes, as well as paths given as
10
+ # Strings and Pathnames.
11
+ #
12
+ # It's probably needlessly comprehensive and complex. Help is appreciated.
13
+ #
14
+ class SanitizedFile
15
+ include CarrierWave::Utilities::FileName
16
+
17
+ attr_reader :file
18
+
19
+ class << self
20
+ attr_writer :sanitize_regexp
21
+
22
+ def sanitize_regexp
23
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
24
+ end
25
+ end
26
+
27
+ def initialize(file)
28
+ self.file = file
29
+ @content = @content_type = nil
30
+ end
31
+
32
+ ##
33
+ # Returns the filename as is, without sanitizing it.
34
+ #
35
+ # === Returns
36
+ #
37
+ # [String] the unsanitized filename
38
+ #
39
+ def original_filename
40
+ return @original_filename if @original_filename
41
+ if @file && @file.respond_to?(:original_filename)
42
+ @file.original_filename
43
+ elsif path
44
+ File.basename(path)
45
+ end
46
+ end
47
+
48
+ ##
49
+ # Returns the filename, sanitized to strip out any evil characters.
50
+ #
51
+ # === Returns
52
+ #
53
+ # [String] the sanitized filename
54
+ #
55
+ def filename
56
+ sanitize(original_filename) if original_filename
57
+ end
58
+
59
+ alias_method :identifier, :filename
60
+
61
+ ##
62
+ # Returns the file's size.
63
+ #
64
+ # === Returns
65
+ #
66
+ # [Integer] the file's size in bytes.
67
+ #
68
+ def size
69
+ if is_path?
70
+ exists? ? File.size(path) : 0
71
+ elsif @file.respond_to?(:size)
72
+ @file.size
73
+ elsif path
74
+ exists? ? File.size(path) : 0
75
+ else
76
+ 0
77
+ end
78
+ end
79
+
80
+ ##
81
+ # Returns the full path to the file. If the file has no path, it will return nil.
82
+ #
83
+ # === Returns
84
+ #
85
+ # [String, nil] the path where the file is located.
86
+ #
87
+ def path
88
+ return if @file.blank?
89
+ if is_path?
90
+ File.expand_path(@file)
91
+ elsif @file.respond_to?(:path) && !@file.path.blank?
92
+ File.expand_path(@file.path)
93
+ end
94
+ end
95
+
96
+ ##
97
+ # === Returns
98
+ #
99
+ # [Boolean] whether the file is supplied as a pathname or string.
100
+ #
101
+ def is_path?
102
+ !!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?)
103
+ end
104
+
105
+ ##
106
+ # === Returns
107
+ #
108
+ # [Boolean] whether the file is valid and has a non-zero size
109
+ #
110
+ def empty?
111
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
112
+ end
113
+
114
+ ##
115
+ # === Returns
116
+ #
117
+ # [Boolean] Whether the file exists
118
+ #
119
+ def exists?
120
+ self.path.present? && File.exist?(self.path)
121
+ end
122
+
123
+ ##
124
+ # Returns the contents of the file.
125
+ #
126
+ # === Returns
127
+ #
128
+ # [String] contents of the file
129
+ #
130
+ def read(*args)
131
+ if args.empty?
132
+ if @content
133
+ @content
134
+ elsif is_path?
135
+ File.open(@file, "rb") {|file| file.read }
136
+ else
137
+ @file.try(:rewind)
138
+ @content = @file.read
139
+ @file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
140
+ @content
141
+ end
142
+ else
143
+ if is_path?
144
+ @file = File.open(path, "rb")
145
+ elsif @file.is_a?(CarrierWave::Uploader::Base)
146
+ @file = StringIO.new(@file.read)
147
+ end
148
+
149
+ @file.read(*args)
150
+ end
151
+ end
152
+
153
+ ##
154
+ # Rewinds the underlying file.
155
+ #
156
+ def rewind
157
+ @file.rewind if @file.respond_to?(:rewind)
158
+ end
159
+
160
+ ##
161
+ # Moves the file to the given path
162
+ #
163
+ # === Parameters
164
+ #
165
+ # [new_path (String)] The path where the file should be moved.
166
+ # [permissions (Integer)] permissions to set on the file in its new location.
167
+ # [directory_permissions (Integer)] permissions to set on created directories.
168
+ #
169
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
170
+ return if self.empty?
171
+ new_path = File.expand_path(new_path)
172
+
173
+ mkdir!(new_path, directory_permissions)
174
+ move!(new_path)
175
+ chmod!(new_path, permissions)
176
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
177
+ self
178
+ end
179
+
180
+ ##
181
+ # Helper to move file to new path.
182
+ #
183
+ def move!(new_path)
184
+ if exists?
185
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
186
+ else
187
+ File.open(new_path, "wb") { |f| f.write(read) }
188
+ end
189
+ end
190
+
191
+ ##
192
+ # Creates a copy of this file and moves it to the given path. Returns the copy.
193
+ #
194
+ # === Parameters
195
+ #
196
+ # [new_path (String)] The path where the file should be copied to.
197
+ # [permissions (Integer)] permissions to set on the copy
198
+ # [directory_permissions (Integer)] permissions to set on created directories.
199
+ #
200
+ # === Returns
201
+ #
202
+ # @return [CarrierWave::SanitizedFile] the location where the file will be stored.
203
+ #
204
+ def copy_to(new_path, permissions=nil, directory_permissions=nil)
205
+ return if self.empty?
206
+ new_path = File.expand_path(new_path)
207
+
208
+ mkdir!(new_path, directory_permissions)
209
+ copy!(new_path)
210
+ chmod!(new_path, permissions)
211
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
212
+ end
213
+
214
+ ##
215
+ # Helper to create copy of file in new path.
216
+ #
217
+ def copy!(new_path)
218
+ if exists?
219
+ FileUtils.cp(path, new_path) unless new_path == path
220
+ else
221
+ File.open(new_path, "wb") { |f| f.write(read) }
222
+ end
223
+ end
224
+
225
+ ##
226
+ # Removes the file from the filesystem.
227
+ #
228
+ def delete
229
+ FileUtils.rm(self.path) if exists?
230
+ end
231
+
232
+ ##
233
+ # Returns a File object, or nil if it does not exist.
234
+ #
235
+ # === Returns
236
+ #
237
+ # [File] a File object representing the SanitizedFile
238
+ #
239
+ def to_file
240
+ return @file if @file.is_a?(File)
241
+ File.open(path, "rb") if exists?
242
+ end
243
+
244
+ ##
245
+ # Returns the content type of the file.
246
+ #
247
+ # === Returns
248
+ #
249
+ # [String] the content type of the file
250
+ #
251
+ def content_type
252
+ @content_type ||=
253
+ identified_content_type ||
254
+ declared_content_type ||
255
+ guessed_safe_content_type ||
256
+ Marcel::MimeType::BINARY
257
+ end
258
+
259
+ ##
260
+ # Sets the content type of the file.
261
+ #
262
+ # === Returns
263
+ #
264
+ # [String] the content type of the file
265
+ #
266
+ def content_type=(type)
267
+ @content_type = type
268
+ end
269
+
270
+ ##
271
+ # Used to sanitize the file name. Public to allow overriding for non-latin characters.
272
+ #
273
+ # === Returns
274
+ #
275
+ # [Regexp] the regexp for sanitizing the file name
276
+ #
277
+ def sanitize_regexp
278
+ CarrierWave::SanitizedFile.sanitize_regexp
279
+ end
280
+
281
+ private
282
+
283
+ def file=(file)
284
+ if file.is_a?(Hash)
285
+ @file = file["tempfile"] || file[:tempfile]
286
+ @original_filename = file["filename"] || file[:filename]
287
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
288
+ else
289
+ @file = file
290
+ @original_filename = nil
291
+ @declared_content_type = nil
292
+ end
293
+ end
294
+
295
+ # create the directory if it doesn't exist
296
+ def mkdir!(path, directory_permissions)
297
+ options = {}
298
+ options[:mode] = directory_permissions if directory_permissions
299
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
300
+ end
301
+
302
+ def chmod!(path, permissions)
303
+ File.chmod(permissions, path) if permissions
304
+ end
305
+
306
+ # Sanitize the filename, to prevent hacking
307
+ def sanitize(name)
308
+ name = name.scrub
309
+ name = name.tr("\\", "/") # work-around for IE
310
+ name = File.basename(name)
311
+ name = name.gsub(sanitize_regexp, "_")
312
+ name = "_#{name}" if name =~ /\A\.+\z/
313
+ name = "unnamed" if name.size.zero?
314
+ name.to_s
315
+ end
316
+
317
+ def declared_content_type
318
+ @declared_content_type ||
319
+ if @file.respond_to?(:content_type) && @file.content_type
320
+ Marcel::MimeType.for(declared_type: @file.content_type.to_s.chomp)
321
+ end
322
+ end
323
+
324
+ # Guess content type from its file extension. Limit what to be returned to prevent spoofing.
325
+ def guessed_safe_content_type
326
+ return unless path
327
+
328
+ type = Marcel::Magic.by_path(original_filename).to_s
329
+ type if type.start_with?('text/') || type.start_with?('application/json')
330
+ end
331
+
332
+ def identified_content_type
333
+ with_io do |io|
334
+ mimetype_by_magic = Marcel::Magic.by_magic(io)
335
+ mimetype_by_path = Marcel::Magic.by_path(path)
336
+
337
+ return nil if mimetype_by_magic.nil?
338
+
339
+ if mimetype_by_path&.child_of?(mimetype_by_magic.type)
340
+ mimetype_by_path.type
341
+ else
342
+ mimetype_by_magic.type
343
+ end
344
+ end
345
+ rescue Errno::ENOENT
346
+ nil
347
+ end
348
+
349
+ def with_io(&block)
350
+ if file.is_a?(IO)
351
+ begin
352
+ yield file
353
+ ensure
354
+ file.try(:rewind)
355
+ end
356
+ elsif path
357
+ File.open(path, &block)
358
+ end
359
+ end
360
+ end # SanitizedFile
361
+ end # CarrierWave
@@ -0,0 +1,43 @@
1
+ module CarrierWave
2
+ module Storage
3
+
4
+ ##
5
+ # This file serves mostly as a specification for Storage engines. There is no requirement
6
+ # that storage engines must be a subclass of this class.
7
+ #
8
+ class Abstract
9
+
10
+ attr_reader :uploader
11
+
12
+ def initialize(uploader)
13
+ @uploader = uploader
14
+ end
15
+
16
+ def identifier
17
+ uploader.deduplicated_filename
18
+ end
19
+
20
+ def store!(file)
21
+ end
22
+
23
+ def retrieve!(identifier)
24
+ end
25
+
26
+ def cache!(new_file)
27
+ raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
28
+ end
29
+
30
+ def retrieve_from_cache!(identifier)
31
+ raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
32
+ end
33
+
34
+ def delete_dir!(path)
35
+ raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
36
+ end
37
+
38
+ def clean_cache!(seconds)
39
+ raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
40
+ end
41
+ end # Abstract
42
+ end # Storage
43
+ end # CarrierWave