1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
# frozen_string_literal: true
module UploadsActions
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
include SendFileUpload
UPLOAD_MOUNTS = %w[avatar attachment file logo pwa_icon header_logo favicon screenshot].freeze
included do
prepend_before_action :set_request_format_from_path_extension
rescue_from FileUploader::InvalidSecret, with: :render_404
rescue_from ::Gitlab::PathTraversal::PathTraversalAttackError do
head :bad_request
end
end
def create
uploader = UploadService.new(model, params[:file], uploader_class).execute
respond_to do |format|
if uploader
format.json do
render json: { link: uploader.to_h }
end
else
format.json do
render json: _('Invalid file.'), status: :unprocessable_entity
end
end
end
end
# This should either
# - send the file directly
# - or redirect to its URL
#
def show
Gitlab::PathTraversal.check_path_traversal!(params[:filename])
return render_404 unless uploader&.exists?
ttl, directives = *cache_settings
ttl ||= 0
directives ||= { private: true, must_revalidate: true }
expires_in ttl, directives
file_uploader = [uploader, *uploader.versions.values].find do |version|
version.filename == params[:filename]
end
return render_404 unless file_uploader
workhorse_set_content_type!
send_upload(file_uploader, attachment: file_uploader.filename, disposition: content_disposition)
end
def authorize
set_workhorse_internal_api_content_type
authorized = uploader_class.workhorse_authorize(
has_length: false,
maximum_size: Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i)
render json: authorized
rescue SocketError
render json: _("Error uploading file"), status: :internal_server_error
end
private
# Based on ActionDispatch::Http::MimeNegotiation. We have an
# initializer that monkey-patches this method out (so that repository
# paths don't guess a format based on extension), but we do want this
# behavior when serving uploads.
def set_request_format_from_path_extension
path = request.headers['action_dispatch.original_path'] || request.headers['PATH_INFO']
return unless match = path&.match(/\.(\w+)\z/)
format = Mime[match.captures.first]
request.format = format.symbol if format
end
def content_disposition
if uploader.embeddable? || uploader.pdf?
'inline'
else
'attachment'
end
end
def uploader_class
raise NotImplementedError
end
def upload_mount
mounted_as = params[:mounted_as]
mounted_as if UPLOAD_MOUNTS.include?(mounted_as)
end
def uploader_mounted?
upload_model_class < CarrierWave::Mount::Extension && !upload_mount.nil?
end
def uploader
if uploader_mounted?
model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
else
build_uploader_from_upload || build_uploader_from_params
end
end
strong_memoize_attr :uploader
# rubocop: disable CodeReuse/ActiveRecord
def build_uploader_from_upload
return unless uploader = build_uploader
upload_paths = uploader.upload_paths(params[:filename])
upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths)
upload&.retrieve_uploader
end
# rubocop: enable CodeReuse/ActiveRecord
def build_uploader_from_params
return unless uploader = build_uploader
uploader.retrieve_from_store!(params[:filename])
Gitlab::AppJsonLogger.info(
message: 'Deprecated usage of build_uploader_from_params',
uploader_class: uploader.class.name,
path: params[:filename],
exists: uploader.exists?
)
uploader
end
def build_uploader
return unless params[:secret] && params[:filename]
uploader = uploader_class.new(model, secret: params[:secret])
return unless uploader.model_valid?
uploader
end
def embeddable?
uploader && uploader.exists? && uploader.embeddable?
end
def bypass_auth_checks_on_uploads?
if target_project && !target_project.public? && target_project.enforce_auth_checks_on_uploads?
return false
end
action_name == 'show' && embeddable?
end
def target_project
nil
end
def find_model
nil
end
def cache_settings
[]
end
def model
find_model
end
strong_memoize_attr :model
def workhorse_authorize_request?
action_name == 'authorize'
end
end
|