uploads_actions.rb 3.97 KB
Newer Older
1
2
# frozen_string_literal: true

3
module UploadsActions
4
  extend ActiveSupport::Concern
Jarka Kadlecova's avatar
Jarka Kadlecova committed
5
  include Gitlab::Utils::StrongMemoize
6
  include SendFileUpload
Jarka Kadlecova's avatar
Jarka Kadlecova committed
7

8
  UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
9

10
11
  included do
    prepend_before_action :set_request_format_from_path_extension
12
    rescue_from FileUploader::InvalidSecret, with: :render_404
13
14
  end

15
  def create
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
16
    uploader = UploadService.new(model, params[:file], uploader_class).execute
17
18

    respond_to do |format|
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
19
      if uploader
20
        format.json do
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
21
          render json: { link: uploader.to_h }
22
23
24
        end
      else
        format.json do
25
          render json: _('Invalid file.'), status: :unprocessable_entity
26
27
28
29
30
        end
      end
    end
  end

31
32
33
34
  # This should either
  #   - send the file directly
  #   - or redirect to its URL
  #
35
  def show
36
    return render_404 unless uploader&.exists?
37

38
39
40
41
42
    # We need to reset caching from the applications controller to get rid of the no-store value
    headers['Cache-Control'] = ''
    headers['Pragma'] = ''

    ttl, directives = *cache_settings
43
    ttl ||= 0
44
45
46
    directives ||= { private: true, must_revalidate: true }

    expires_in ttl, directives
47

48
49
50
    file_uploader = [uploader, *uploader.versions.values].find do |version|
      version.filename == params[:filename]
    end
51

52
    return render_404 unless file_uploader
53

54
    workhorse_set_content_type!
55
    send_upload(file_uploader, attachment: file_uploader.filename, disposition: content_disposition)
56
  end
Jarka Kadlecova's avatar
Jarka Kadlecova committed
57

58
59
60
61
62
63
64
65
  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
66
  rescue SocketError
67
    render json: _("Error uploading file"), status: :internal_server_error
68
69
  end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
70
71
  private

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
  # 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']

    if match = path&.match(/\.(\w+)\z/)
      format = Mime[match.captures.first]

      request.format = format.symbol if format
    end
  end

  def content_disposition
    if uploader.embeddable? || uploader.pdf?
      'inline'
    else
      'attachment'
    end
  end

94
95
96
97
98
99
100
101
102
103
104
105
106
  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

Jarka Kadlecova's avatar
Jarka Kadlecova committed
107
108
  def uploader
    strong_memoize(:uploader) do
109
110
111
112
113
114
115
      if uploader_mounted?
        model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
      else
        build_uploader_from_upload || build_uploader_from_params
      end
    end
  end
Jarka Kadlecova's avatar
Jarka Kadlecova committed
116

117
  # rubocop: disable CodeReuse/ActiveRecord
118
  def build_uploader_from_upload
119
    return unless uploader = build_uploader
Jarka Kadlecova's avatar
Jarka Kadlecova committed
120

121
    upload_paths = uploader.upload_paths(params[:filename])
122
    upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths)
123
    upload&.retrieve_uploader
124
  end
125
  # rubocop: enable CodeReuse/ActiveRecord
126
127

  def build_uploader_from_params
128
129
130
131
132
133
134
135
136
    return unless uploader = build_uploader

    uploader.retrieve_from_store!(params[:filename])
    uploader
  end

  def build_uploader
    return unless params[:secret] && params[:filename]

137
    uploader = uploader_class.new(model, secret: params[:secret])
138

139
    return unless uploader.model_valid?
140

141
    uploader
Jarka Kadlecova's avatar
Jarka Kadlecova committed
142
143
  end

144
145
  def embeddable?
    uploader && uploader.exists? && uploader.embeddable?
Jarka Kadlecova's avatar
Jarka Kadlecova committed
146
147
  end

148
149
150
151
  def find_model
    nil
  end

152
153
  def cache_settings
    []
154
155
  end

156
157
  def model
    strong_memoize(:model) { find_model }
Jarka Kadlecova's avatar
Jarka Kadlecova committed
158
  end
159
160
161
162

  def workhorse_authorize_request?
    action_name == 'authorize'
  end
163
end