Class: FileUploader
- Inherits:
-
GitlabUploader
- Object
- CarrierWave::Uploader::Base
- GitlabUploader
- FileUploader
- Includes:
- ObjectStorage::Concern, ObjectStorage::Extension::RecordsUploads, RecordsUploads::Concern, UploaderHelper
- Defined in:
- app/uploaders/file_uploader.rb
Overview
This class breaks the actual CarrierWave concept. Every uploader should use a base_dir that is model agnostic so we can build back URLs from base_dir-relative paths saved in the ‘Upload` model.
As the ‘.base_dir` is model dependent and not saved in the upload model (see #upload_path) there is no way to build back the correct file path without the model, which defies CarrierWave way of storing files.
Direct Known Subclasses
Constant Summary collapse
- MARKDOWN_PATTERN =
This pattern is vulnerable to malicious inputs, so use Gitlab::UntrustedRegexp to place bounds on execution time
Gitlab::UntrustedRegexp.new( '!?\[.*?\]\(/uploads/(?P<secret>[0-9a-f]{32})/(?P<file>.*?)\)' )
- DYNAMIC_PATH_PATTERN =
%r{.*(?<secret>\b(\h{10}|\h{32}))\/(?<identifier>.*)}
- VALID_SECRET_PATTERN =
%r{\A\h{10,32}\z}
- InvalidSecret =
Class.new(StandardError)
Constants included from Gitlab::FileTypeDetection
Gitlab::FileTypeDetection::DANGEROUS_AUDIO_EXT, Gitlab::FileTypeDetection::DANGEROUS_IMAGE_EXT, Gitlab::FileTypeDetection::DANGEROUS_VIDEO_EXT, Gitlab::FileTypeDetection::PDF_EXT, Gitlab::FileTypeDetection::SAFE_AUDIO_EXT, Gitlab::FileTypeDetection::SAFE_IMAGE_EXT, Gitlab::FileTypeDetection::SAFE_IMAGE_FOR_SCALING_EXT, Gitlab::FileTypeDetection::SAFE_VIDEO_EXT
Constants inherited from GitlabUploader
GitlabUploader::ObjectNotReadyError, GitlabUploader::PROTECTED_METHODS
Instance Attribute Summary collapse
-
#model ⇒ Object
Returns the value of attribute model.
Attributes included from RecordsUploads::Concern
Class Method Summary collapse
-
.absolute_base_dir(model) ⇒ Object
used in migrations and import/exports.
- .absolute_path(upload) ⇒ Object
- .base_dir(model, store = Store::LOCAL) ⇒ Object
-
.copy_to(uploader, to_project) ⇒ Object
return a new uploader with a file copy on another project.
- .extract_dynamic_path(path) ⇒ Object
- .generate_secret ⇒ Object
-
.model_path_segment(model) ⇒ Object
Returns the part of ‘store_dir` that can change based on the model’s current path.
- .relative_path(upload) ⇒ Object
- .root ⇒ Object
Instance Method Summary collapse
-
#absolute_path ⇒ Object
we don’t need to know the actual path, an uploader instance should be able to yield the file content on demand, so we should build the digest.
-
#base_dir(store = nil) ⇒ Object
enforce the usage of Hashed storage when storing to remote store as the FileMover doesn’t support OS.
- #copy_file(file) ⇒ Object
-
#initialize(model, mounted_as = nil, **uploader_context) ⇒ FileUploader
constructor
A new instance of FileUploader.
- #initialize_copy(from) ⇒ Object
- #local_storage_path(file_identifier) ⇒ Object
- #remote_storage_path(file_identifier) ⇒ Object
- #secret ⇒ Object
- #store_dirs ⇒ Object
- #to_h ⇒ Object
- #upload=(value) ⇒ Object
- #upload_path ⇒ Object
- #upload_paths(identifier) ⇒ Object
Methods included from ObjectStorage::Concern
#cache!, #delete_migrated_file, #delete_tmp_file_after_storage, #exclusive_lease_key, #exists?, #file_cache_storage?, #file_storage?, #filename, #filename=, #fog_attributes, #fog_credentials, #fog_directory, #fog_public, #migrate!, #object_store, #object_store=, #persist_object_store!, #persist_object_store?, #retrieve_from_store!, #store!, #store_dir, #store_path, #use_file, #use_open_file
Methods included from Gitlab::Utils::Override
#extended, extensions, #included, #method_added, #override, #prepended, #queue_verification, verify!
Methods included from RecordsUploads::Concern
#filename, #readd_upload, #record_upload
Methods included from Gitlab::FileMarkdownLinkBuilder
#markdown_link, #markdown_name
Methods included from Gitlab::FileTypeDetection
#audio?, #dangerous_audio?, #dangerous_embeddable?, #dangerous_image?, #dangerous_video?, #embeddable?, extension_match?, #image?, #image_safe_for_scaling?, #pdf?, #video?
Methods included from ObjectStorage::Extension::RecordsUploads
#exclusive_lease_key, #retrieve_from_store!
Methods inherited from GitlabUploader
#cache_dir, #cached_size, #exists?, #file_cache_storage?, file_storage?, #filename, #local_url, #model_valid?, #move_to_cache, #move_to_store, #multi_read, #open, #options, options, #relative_path, #replace_file_without_saving!, storage_location, #url_or_file_path, version, #work_dir
Constructor Details
#initialize(model, mounted_as = nil, **uploader_context) ⇒ FileUploader
Returns a new instance of FileUploader.
94 95 96 97 98 99 |
# File 'app/uploaders/file_uploader.rb', line 94 def initialize(model, mounted_as = nil, **uploader_context) super(model, nil, **uploader_context) @model = model apply_context!(uploader_context) end |
Instance Attribute Details
#model ⇒ Object
Returns the value of attribute model.
92 93 94 |
# File 'app/uploaders/file_uploader.rb', line 92 def model @model end |
Class Method Details
.absolute_base_dir(model) ⇒ Object
used in migrations and import/exports
56 57 58 |
# File 'app/uploaders/file_uploader.rb', line 56 def self.absolute_base_dir(model) File.join(root, base_dir(model)) end |
.absolute_path(upload) ⇒ Object
34 35 36 37 38 39 |
# File 'app/uploaders/file_uploader.rb', line 34 def self.absolute_path(upload) File.join( root, relative_path(upload) ) end |
.base_dir(model, store = Store::LOCAL) ⇒ Object
48 49 50 51 52 53 |
# File 'app/uploaders/file_uploader.rb', line 48 def self.base_dir(model, store = Store::LOCAL) decorated_model = model decorated_model = Storage::Hashed.new(model) if store == Store::REMOTE model_path_segment(decorated_model) end |
.copy_to(uploader, to_project) ⇒ Object
return a new uploader with a file copy on another project
174 175 176 177 178 179 180 181 |
# File 'app/uploaders/file_uploader.rb', line 174 def self.copy_to(uploader, to_project) moved = self.new(to_project) moved.object_store = uploader.object_store moved.filename = uploader.filename moved.copy_file(uploader.file) moved end |
.extract_dynamic_path(path) ⇒ Object
81 82 83 |
# File 'app/uploaders/file_uploader.rb', line 81 def self.extract_dynamic_path(path) DYNAMIC_PATH_PATTERN.match(path) end |
.generate_secret ⇒ Object
77 78 79 |
# File 'app/uploaders/file_uploader.rb', line 77 def self.generate_secret SecureRandom.hex end |
.model_path_segment(model) ⇒ Object
Returns the part of ‘store_dir` that can change based on the model’s current path
This is used to build Upload paths dynamically based on the model’s current namespace and path, allowing us to ignore renames or transfers.
model - Object that responds to ‘full_path` and `disk_path`
Returns a String without a trailing slash
69 70 71 72 73 74 75 |
# File 'app/uploaders/file_uploader.rb', line 69 def self.model_path_segment(model) case model when Storage::Hashed then model.disk_path else model.hashed_storage?(:attachments) ? model.disk_path : model.full_path end end |
.relative_path(upload) ⇒ Object
41 42 43 44 45 46 |
# File 'app/uploaders/file_uploader.rb', line 41 def self.relative_path(upload) File.join( base_dir(upload.model), upload.path # already contain the dynamic_segment, see #upload_path ) end |
.root ⇒ Object
30 31 32 |
# File 'app/uploaders/file_uploader.rb', line 30 def self.root File.join(.storage_path, 'uploads') end |
Instance Method Details
#absolute_path ⇒ Object
we don’t need to know the actual path, an uploader instance should be able to yield the file content on demand, so we should build the digest
116 117 118 |
# File 'app/uploaders/file_uploader.rb', line 116 def absolute_path self.class.absolute_path(@upload) end |
#base_dir(store = nil) ⇒ Object
enforce the usage of Hashed storage when storing to remote store as the FileMover doesn’t support OS
110 111 112 |
# File 'app/uploaders/file_uploader.rb', line 110 def base_dir(store = nil) self.class.base_dir(@model, store || object_store) end |
#copy_file(file) ⇒ Object
183 184 185 186 187 188 189 190 191 192 |
# File 'app/uploaders/file_uploader.rb', line 183 def copy_file(file) to_path = if file_storage? File.join(self.class.root, store_path) else store_path end self.file = file.copy_to(to_path) record_upload # after_store is not triggered end |
#initialize_copy(from) ⇒ Object
101 102 103 104 105 106 |
# File 'app/uploaders/file_uploader.rb', line 101 def initialize_copy(from) super @secret = self.class.generate_secret @upload = nil # calling record_upload would delete the old upload if set end |
#local_storage_path(file_identifier) ⇒ Object
129 130 131 |
# File 'app/uploaders/file_uploader.rb', line 129 def local_storage_path(file_identifier) File.join(dynamic_segment, file_identifier) end |
#remote_storage_path(file_identifier) ⇒ Object
133 134 135 |
# File 'app/uploaders/file_uploader.rb', line 133 def remote_storage_path(file_identifier) File.join(store_dir, file_identifier) end |
#secret ⇒ Object
165 166 167 168 169 170 171 |
# File 'app/uploaders/file_uploader.rb', line 165 def secret @secret ||= self.class.generate_secret raise InvalidSecret unless VALID_SECRET_PATTERN.match?(@secret) @secret end |
#store_dirs ⇒ Object
137 138 139 140 141 142 |
# File 'app/uploaders/file_uploader.rb', line 137 def store_dirs { Store::LOCAL => File.join(base_dir, dynamic_segment), Store::REMOTE => File.join(base_dir(ObjectStorage::Store::REMOTE), dynamic_segment) } end |
#to_h ⇒ Object
144 145 146 147 148 149 150 |
# File 'app/uploaders/file_uploader.rb', line 144 def to_h { alt: markdown_name, url: secure_url, markdown: markdown_link } end |
#upload=(value) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'app/uploaders/file_uploader.rb', line 152 def upload=(value) super return unless value return if apply_context!(value.uploader_context) # fallback to the regex based extraction if matches = self.class.extract_dynamic_path(value.path) @secret = matches[:secret] @identifier = matches[:identifier] end end |
#upload_path ⇒ Object
120 121 122 123 124 125 126 127 |
# File 'app/uploaders/file_uploader.rb', line 120 def upload_path if file_storage? # Legacy path relative to project.full_path local_storage_path(identifier) else remote_storage_path(identifier) end end |
#upload_paths(identifier) ⇒ Object
85 86 87 88 89 90 |
# File 'app/uploaders/file_uploader.rb', line 85 def upload_paths(identifier) [ File.join(secret, identifier), File.join(base_dir(Store::REMOTE), secret, identifier) ] end |