summaryrefslogtreecommitdiff
path: root/app/controllers/concerns/uploads_actions.rb
blob: 9b3b2c4a48287a3bbb50a7dc5645a140e591e384 (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
# frozen_string_literal: true

module UploadsActions
  extend ActiveSupport::Concern
  include Gitlab::Utils::StrongMemoize
  include SendFileUpload

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

  included do
    prepend_before_action :set_request_format_from_path_extension
  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?

    # 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
    ttl ||= 0
    directives ||= { private: true, must_revalidate: true }

    expires_in ttl, directives

    disposition = uploader.embeddable? ? 'inline' : 'attachment'

    uploaders = [uploader, *uploader.versions.values]
    uploader = uploaders.find { |version| version.filename == params[:filename] }

    return render_404 unless uploader

    workhorse_set_content_type!
    send_upload(uploader, attachment: uploader.filename, disposition: 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']

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

      request.format = format.symbol if format
    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
    strong_memoize(:uploader) do
      if uploader_mounted?
        model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
      else
        build_uploader_from_upload || build_uploader_from_params
      end
    end
  end

  # 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 find_model
    nil
  end

  def cache_settings
    []
  end

  def model
    strong_memoize(:model) { find_model }
  end

  def workhorse_authorize_request?
    action_name == 'authorize'
  end
end