summaryrefslogtreecommitdiff
path: root/app/controllers/concerns/uploads_actions.rb
blob: 308da018a42e2bb31b11cc9af69bc3bc0f111eb0 (plain)
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
# 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].freeze

  included do
    prepend_before_action :set_request_format_from_path_extension
    skip_before_action :default_cache_headers, only: :show
    rescue_from FileUploader::InvalidSecret, with: :render_404
  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
    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])
    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