salebot_uploader 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +0 -0
  3. data/lib/generators/templates/uploader.rb.erb +9 -0
  4. data/lib/generators/uploader_generator.rb +7 -0
  5. data/lib/salebot_uploader/compatibility/paperclip.rb +104 -0
  6. data/lib/salebot_uploader/downloader/base.rb +101 -0
  7. data/lib/salebot_uploader/downloader/remote_file.rb +68 -0
  8. data/lib/salebot_uploader/error.rb +8 -0
  9. data/lib/salebot_uploader/locale/en.yml +17 -0
  10. data/lib/salebot_uploader/mount.rb +446 -0
  11. data/lib/salebot_uploader/mounter.rb +255 -0
  12. data/lib/salebot_uploader/orm/activerecord.rb +68 -0
  13. data/lib/salebot_uploader/processing/mini_magick.rb +194 -0
  14. data/lib/salebot_uploader/processing/rmagick.rb +402 -0
  15. data/lib/salebot_uploader/processing/vips.rb +284 -0
  16. data/lib/salebot_uploader/processing.rb +3 -0
  17. data/lib/salebot_uploader/sanitized_file.rb +357 -0
  18. data/lib/salebot_uploader/storage/abstract.rb +41 -0
  19. data/lib/salebot_uploader/storage/file.rb +124 -0
  20. data/lib/salebot_uploader/storage/fog.rb +547 -0
  21. data/lib/salebot_uploader/storage.rb +3 -0
  22. data/lib/salebot_uploader/test/matchers.rb +398 -0
  23. data/lib/salebot_uploader/uploader/cache.rb +223 -0
  24. data/lib/salebot_uploader/uploader/callbacks.rb +33 -0
  25. data/lib/salebot_uploader/uploader/configuration.rb +184 -0
  26. data/lib/salebot_uploader/uploader/content_type_allowlist.rb +61 -0
  27. data/lib/salebot_uploader/uploader/content_type_denylist.rb +62 -0
  28. data/lib/salebot_uploader/uploader/default_url.rb +17 -0
  29. data/lib/salebot_uploader/uploader/dimension.rb +66 -0
  30. data/lib/salebot_uploader/uploader/download.rb +24 -0
  31. data/lib/salebot_uploader/uploader/extension_allowlist.rb +63 -0
  32. data/lib/salebot_uploader/uploader/extension_denylist.rb +64 -0
  33. data/lib/salebot_uploader/uploader/file_size.rb +43 -0
  34. data/lib/salebot_uploader/uploader/mountable.rb +44 -0
  35. data/lib/salebot_uploader/uploader/processing.rb +125 -0
  36. data/lib/salebot_uploader/uploader/proxy.rb +99 -0
  37. data/lib/salebot_uploader/uploader/remove.rb +21 -0
  38. data/lib/salebot_uploader/uploader/serialization.rb +28 -0
  39. data/lib/salebot_uploader/uploader/store.rb +142 -0
  40. data/lib/salebot_uploader/uploader/url.rb +44 -0
  41. data/lib/salebot_uploader/uploader/versions.rb +350 -0
  42. data/lib/salebot_uploader/uploader.rb +53 -0
  43. data/lib/salebot_uploader/utilities/file_name.rb +47 -0
  44. data/lib/salebot_uploader/utilities/uri.rb +26 -0
  45. data/lib/salebot_uploader/utilities.rb +7 -0
  46. data/lib/salebot_uploader/validations/active_model.rb +76 -0
  47. data/lib/salebot_uploader/version.rb +3 -0
  48. data/lib/salebot_uploader.rb +62 -0
  49. metadata +392 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2f8f0cc7d88364cc565369c94e3d803fa1fa07698e6f387bb4c032bfb83eccd2
4
+ data.tar.gz: 3ba9ec518e3cacac6a77f371b1cdcf3fe6c8410f0625c6b6f1289b6c786b9f77
5
+ SHA512:
6
+ metadata.gz: 6f4f66bff7588946e9ecffa6793d33ce19f898d138c5a40827691c59d414c7dee4a951d8e61d363aba2c192a17a3dd515adb3557dcdc6f358df522e0f45821b9
7
+ data.tar.gz: ed89430e4376646d1351dd9f5ad5b8731231bdf0016001c2ade8c9e07e0539cc4d5bd2a89cbec4277975bf0533a30b8c376f4c656ce6bad87ec4f3423a26320f
data/README.md ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ class <%= class_name %>Uploader < SalebotUploader::Uploader::Base
2
+
3
+ storage :file
4
+ # Переопределить каталог, в котором будут храниться загруженные файлы.
5
+ # Это разумное значение по умолчанию для загрузчиков, которые предназначены для монтирования:
6
+ def store_dir
7
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ class UploaderGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ def create_uploader_file
5
+ template "uploader.rb.erb", File.join('app/uploaders', class_path, "#{file_name}_uploader.rb")
6
+ end
7
+ end
@@ -0,0 +1,104 @@
1
+ module SalebotUploader
2
+ module Compatibility
3
+
4
+ ##
5
+ # Mix this module into an Uploader to make it mimic Paperclip's storage paths
6
+ # This will make your Uploader use the same default storage path as paperclip
7
+ # does. If you need to override it, you can override the +paperclip_path+ method
8
+ # and provide a Paperclip style path:
9
+ #
10
+ # class MyUploader < SalebotUploader::Uploader::Base
11
+ # include SalebotUploader::Compatibility::Paperclip
12
+ #
13
+ # def paperclip_path
14
+ # ":rails_root/public/uploads/:id/:attachment/:style_:basename.:extension"
15
+ # end
16
+ # end
17
+ #
18
+ # ---
19
+ #
20
+ # This file contains code taken from Paperclip
21
+ #
22
+ # LICENSE
23
+ #
24
+ # The MIT License
25
+ #
26
+ # Copyright (c) 2008 Jon Yurek and thoughtbot, inc.
27
+ #
28
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
29
+ # of this software and associated documentation files (the "Software"), to deal
30
+ # in the Software without restriction, including without limitation the rights
31
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32
+ # copies of the Software, and to permit persons to whom the Software is
33
+ # furnished to do so, subject to the following conditions:
34
+ #
35
+ # The above copyright notice and this permission notice shall be included in
36
+ # all copies or substantial portions of the Software.
37
+ #
38
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
44
+ # THE SOFTWARE.
45
+ #
46
+ module Paperclip
47
+ extend ActiveSupport::Concern
48
+
49
+ DEFAULT_MAPPINGS = {
50
+ :rails_root => lambda{|u, f| Rails.root.to_s },
51
+ :rails_env => lambda{|u, f| Rails.env },
52
+ :id_partition => lambda{|u, f| ('%09d' % u.model.id).scan(/\d{3}/).join('/')},
53
+ :id => lambda{|u, f| u.model.id },
54
+ :attachment => lambda{|u, f| u.mounted_as.to_s.downcase.pluralize },
55
+ :style => lambda{|u, f| u.paperclip_style },
56
+ :basename => lambda{|u, f| u.filename.gsub(/#{File.extname(u.filename)}$/, '') },
57
+ :extension => lambda{|u, d| File.extname(u.filename).gsub(/^\.+/, '')},
58
+ :class => lambda{|u, f| u.model.class.name.underscore.pluralize}
59
+ }
60
+
61
+ included do
62
+ attr_accessor :filename
63
+
64
+ class_attribute :mappings
65
+ self.mappings ||= DEFAULT_MAPPINGS.dup
66
+ end
67
+
68
+ def store_path(for_file=filename)
69
+ path = paperclip_path
70
+ self.filename = for_file
71
+ path ||= File.join(*[store_dir, paperclip_style.to_s, for_file].compact)
72
+ interpolate_paperclip_path(path)
73
+ end
74
+
75
+ def store_dir
76
+ ':rails_root/public/system/:attachment/:id'
77
+ end
78
+
79
+ def paperclip_default_style
80
+ :original
81
+ end
82
+
83
+ def paperclip_path; end
84
+
85
+ def paperclip_style
86
+ version_name || paperclip_default_style
87
+ end
88
+
89
+ module ClassMethods
90
+ def interpolate(sym, &block)
91
+ mappings[sym] = block
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def interpolate_paperclip_path(path)
98
+ mappings.each_pair.inject(path) do |agg, pair|
99
+ agg.gsub(":#{pair[0]}") { pair[1].call(self, self.paperclip_style).to_s }
100
+ end
101
+ end
102
+ end # Paperclip
103
+ end # Compatibility
104
+ end # SalebotUploader
@@ -0,0 +1,101 @@
1
+ require 'open-uri'
2
+ require 'ssrf_filter'
3
+ require 'addressable'
4
+ require 'salebot_uploader/downloader/remote_file'
5
+
6
+ module SalebotUploader
7
+ module Downloader
8
+ class Base
9
+ include SalebotUploader::Utilities::Uri
10
+
11
+ attr_reader :uploader
12
+
13
+ def initialize(uploader)
14
+ @uploader = uploader
15
+ end
16
+
17
+ ##
18
+ # Downloads a file from given URL and returns a RemoteFile.
19
+ #
20
+ # === Parameters
21
+ #
22
+ # [url (String)] The URL where the remote file is stored
23
+ # [remote_headers (Hash)] Request headers
24
+ #
25
+ def download(url, remote_headers = {})
26
+ @current_download_retry_count = 0
27
+ headers = remote_headers.
28
+ reverse_merge('User-Agent' => "SalebotUploader/#{SalebotUploader::VERSION}")
29
+ uri = process_uri(url.to_s)
30
+ begin
31
+ if skip_ssrf_protection?(uri)
32
+ response = OpenURI.open_uri(process_uri(url.to_s), headers)
33
+ else
34
+ request = nil
35
+ if ::SsrfFilter::VERSION.to_f < 1.1
36
+ response = SsrfFilter.get(uri, headers: headers) do |req|
37
+ request = req
38
+ end
39
+ else
40
+ response = SsrfFilter.get(uri, headers: headers, request_proc: ->(req) { request = req }) do |res|
41
+ res.body # ensure to read body
42
+ end
43
+ end
44
+ response.uri = request.uri
45
+ response.value
46
+ end
47
+ rescue StandardError => e
48
+ if @current_download_retry_count < @uploader.download_retry_count
49
+ @current_download_retry_count += 1
50
+ sleep @uploader.download_retry_wait_time
51
+ retry
52
+ else
53
+ raise SalebotUploader::DownloadError, "could not download file: #{e.message}"
54
+ end
55
+ end
56
+ SalebotUploader::Downloader::RemoteFile.new(response)
57
+ end
58
+
59
+ ##
60
+ # Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
61
+ #
62
+ # === Parameters
63
+ #
64
+ # [url (String)] The URL where the remote file is stored
65
+ #
66
+ def process_uri(source)
67
+ uri = Addressable::URI.parse(source)
68
+ uri.host = uri.normalized_host
69
+ # Perform decode first, as the path is likely to be already encoded
70
+ uri.path = encode_path(decode_uri(uri.path)) if uri.path =~ SalebotUploader::Utilities::Uri::PATH_UNSAFE
71
+ uri.query = encode_non_ascii(uri.query) if uri.query
72
+ uri.fragment = encode_non_ascii(uri.fragment) if uri.fragment
73
+ URI.parse(uri.to_s)
74
+ rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
75
+ raise SalebotUploader::DownloadError, "couldn't parse URL: #{source}"
76
+ end
77
+
78
+ ##
79
+ # If this returns true, SSRF protection will be bypassed.
80
+ # You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
81
+ #
82
+ # === Parameters
83
+ #
84
+ # [uri (URI)] The URI where the remote file is stored
85
+ #
86
+ # === Examples
87
+ #
88
+ # class SalebotUploader::Downloader::CustomDownloader < SalebotUploader::Downloader::Base
89
+ # def skip_ssrf_protection?(uri)
90
+ # uri.hostname == 'localhost' && uri.port == 80
91
+ # end
92
+ # end
93
+ #
94
+ # my_uploader.downloader = SalebotUploader::Downloader::CustomDownloader
95
+ #
96
+ def skip_ssrf_protection?(uri)
97
+ @uploader.skip_ssrf_protection
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,68 @@
1
+ module SalebotUploader
2
+ module Downloader
3
+ class RemoteFile
4
+ attr_reader :file, :uri
5
+
6
+ def initialize(file)
7
+ case file
8
+ when String
9
+ @file = StringIO.new(file)
10
+ when Net::HTTPResponse
11
+ body = file.body
12
+ raise SalebotUploader::DownloadError, 'could not download file: No Content' if body.nil?
13
+
14
+ @file = StringIO.new(body)
15
+ @content_type = file.content_type
16
+ @headers = file
17
+ @uri = file.uri
18
+ else
19
+ @file = file
20
+ @content_type = file.content_type
21
+ @headers = file.meta
22
+ @uri = file.base_uri
23
+ end
24
+ end
25
+
26
+ def content_type
27
+ @content_type || 'application/octet-stream'
28
+ end
29
+
30
+ def headers
31
+ @headers || {}
32
+ end
33
+
34
+ def original_filename
35
+ filename = filename_from_header || filename_from_uri
36
+ mime_type = Marcel::TYPES[content_type]
37
+ unless File.extname(filename).present? || mime_type.blank?
38
+ extension = mime_type[0].first
39
+ filename = "#{filename}.#{extension}"
40
+ end
41
+ filename
42
+ end
43
+
44
+ private
45
+
46
+ def filename_from_header
47
+ return nil unless headers['content-disposition']
48
+
49
+ match = headers['content-disposition'].match(/filename=(?:"([^"]+)"|([^";]+))/)
50
+ return nil unless match
51
+
52
+ match[1].presence || match[2].presence
53
+ end
54
+
55
+ def filename_from_uri
56
+ CGI.unescape(File.basename(uri.path))
57
+ end
58
+
59
+ def method_missing(*args, &block)
60
+ file.send(*args, &block)
61
+ end
62
+
63
+ def respond_to_missing?(*args)
64
+ super || file.respond_to?(*args)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,8 @@
1
+ module SalebotUploader
2
+ class UploadError < StandardError; end
3
+ class IntegrityError < UploadError; end
4
+ class InvalidParameter < UploadError; end
5
+ class ProcessingError < UploadError; end
6
+ class DownloadError < UploadError; end
7
+ class UnknownStorageError < StandardError; end
8
+ end
@@ -0,0 +1,17 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ salebot_uploader_processing_error: failed to be processed
5
+ salebot_uploader_integrity_error: is not of an allowed file type
6
+ salebot_uploader_download_error: could not be downloaded
7
+ extension_allowlist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
+ extension_denylist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
9
+ content_type_allowlist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
10
+ content_type_denylist_error: "You are not allowed to upload %{content_type} files"
11
+ processing_error: "Failed to manipulate, maybe it is not an image?"
12
+ min_size_error: "File size should be greater than %{min_size}"
13
+ max_size_error: "File size should be less than %{max_size}"
14
+ min_width_error: "Image width should be greater than %{min_width}px"
15
+ max_width_error: "Image width should be less than %{max_width}px"
16
+ min_height_error: "Image height should be greater than %{min_height}px"
17
+ max_height_error: "Image height should be less than %{max_height}px"