Class: UploadedFile

Inherits:
Object
  • Object
show all
Defined in:
lib/uploaded_file.rb

Constant Summary collapse

InvalidPathError =
Class.new(StandardError)
UnknownSizeError =
Class.new(StandardError)
ALLOWED_KWARGS =
%i[filename content_type sha256 remote_id size upload_duration sha1 md5].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, **kwargs) ⇒ UploadedFile

Returns a new instance of UploadedFile.



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
# File 'lib/uploaded_file.rb', line 23

def initialize(path, **kwargs)
  validate_kwargs(kwargs)

  if path.present?
    raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path)

    @tempfile = File.new(path, 'rb')
    @size = @tempfile.size
  else
    begin
      @size = Integer(kwargs[:size])
    rescue ArgumentError, TypeError
      raise UnknownSizeError, 'Unable to determine file size'
    end
  end

  begin
    @upload_duration = Float(kwargs[:upload_duration])
  rescue ArgumentError, TypeError
    @upload_duration = 0
  end

  @content_type = kwargs[:content_type] || 'application/octet-stream'
  @original_filename = sanitize_filename(kwargs[:filename] || path || '')
  @sha256 = kwargs[:sha256]
  @sha1 = kwargs[:sha1]
  @md5 = kwargs[:md5]
  @remote_id = kwargs[:remote_id]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

:nodoc:



110
111
112
# File 'lib/uploaded_file.rb', line 110

def method_missing(method_name, *args, &block) #:nodoc:
  @tempfile.__send__(method_name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end

Instance Attribute Details

#content_typeObject

The content type of the “uploaded” file



19
20
21
# File 'lib/uploaded_file.rb', line 19

def content_type
  @content_type
end

#md5Object (readonly)

Returns the value of attribute md5.



21
22
23
# File 'lib/uploaded_file.rb', line 21

def md5
  @md5
end

#original_filenameObject (readonly)

The filename, not including the path, of the “uploaded” file



13
14
15
# File 'lib/uploaded_file.rb', line 13

def original_filename
  @original_filename
end

#remote_idObject (readonly)

Returns the value of attribute remote_id.



21
22
23
# File 'lib/uploaded_file.rb', line 21

def remote_id
  @remote_id
end

#sha1Object (readonly)

Returns the value of attribute sha1.



21
22
23
# File 'lib/uploaded_file.rb', line 21

def sha1
  @sha1
end

#sha256Object (readonly)

Returns the value of attribute sha256.



21
22
23
# File 'lib/uploaded_file.rb', line 21

def sha256
  @sha256
end

#sizeObject (readonly)

Returns the value of attribute size.



21
22
23
# File 'lib/uploaded_file.rb', line 21

def size
  @size
end

#tempfileObject (readonly)

The tempfile



16
17
18
# File 'lib/uploaded_file.rb', line 16

def tempfile
  @tempfile
end

#upload_durationObject (readonly)

Returns the value of attribute upload_duration.



21
22
23
# File 'lib/uploaded_file.rb', line 21

def upload_duration
  @upload_duration
end

Class Method Details

.allowed_path?(file_path, paths) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
87
88
# File 'lib/uploaded_file.rb', line 84

def self.allowed_path?(file_path, paths)
  paths.any? do |path|
    File.exist?(path) && file_path.start_with?(File.realpath(path))
  end
end

.from_params(params, upload_paths) ⇒ Object



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
# File 'lib/uploaded_file.rb', line 53

def self.from_params(params, upload_paths)
  path = params['path']
  remote_id = params['remote_id']
  return if path.blank? && remote_id.blank?

  # don't use file_path if remote_id is set
  if remote_id.present?
    file_path = nil
  elsif path.present?
    file_path = File.realpath(path)

    unless self.allowed_path?(file_path, Array(upload_paths).compact)
      raise InvalidPathError, "insecure path used '#{file_path}'"
    end
  end

  new(
    file_path,
    filename: params['name'],
    content_type: params['type'] || 'application/octet-stream',
    sha256: params['sha256'],
    remote_id: remote_id,
    size: params['size'],
    upload_duration: params['upload_duration'],
    sha1: params['sha1'],
    md5: params['md5']
  ).tap do |uploaded_file|
    ::Gitlab::Instrumentation::Uploads.track(uploaded_file)
  end
end

Instance Method Details

#closeObject



104
105
106
# File 'lib/uploaded_file.rb', line 104

def close
  @tempfile&.close
end

#pathObject Also known as: local_path



100
101
102
# File 'lib/uploaded_file.rb', line 100

def path
  @tempfile&.path
end

#respond_to?(method_name, include_private = false) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


114
115
116
# File 'lib/uploaded_file.rb', line 114

def respond_to?(method_name, include_private = false) #:nodoc:
  @tempfile.respond_to?(method_name, include_private) || super
end

#sanitize_filename(name) ⇒ Object

copy-pasted from CarrierWave::SanitizedFile



91
92
93
94
95
96
97
98
# File 'lib/uploaded_file.rb', line 91

def sanitize_filename(name)
  name = name.tr("\\", "/") # work-around for IE
  name = ::File.basename(name)
  name = name.gsub(CarrierWave::SanitizedFile.sanitize_regexp, "_")
  name = "_#{name}" if name =~ /\A\.+\z/
  name = "unnamed" if name.empty?
  name.mb_chars.to_s
end