Module: Gitlab::Utils

Extended by:
Utils
Included in:
API::Helpers, Utils
Defined in:
lib/gitlab/utils.rb,
lib/gitlab/utils/gzip.rb,
lib/gitlab/utils/markdown.rb,
lib/gitlab/utils/override.rb,
lib/gitlab/utils/deep_size.rb,
lib/gitlab/utils/measuring.rb,
lib/gitlab/utils/merge_hash.rb,
lib/gitlab/utils/usage_data.rb,
lib/gitlab/utils/inline_hash.rb,
lib/gitlab/utils/strong_memoize.rb,
lib/gitlab/utils/lazy_attributes.rb,
lib/gitlab/utils/safe_inline_hash.rb,
lib/gitlab/utils/log_limited_array.rb,
lib/gitlab/utils/sanitize_node_link.rb,
lib/gitlab/utils/json_size_estimator.rb

Defined Under Namespace

Modules: Gzip, InlineHash, LazyAttributes, LogLimitedArray, Markdown, MergeHash, Override, SanitizeNodeLink, StrongMemoize, UsageData Classes: DeepSize, JsonSizeEstimator, Measuring, SafeInlineHash

Instance Method Summary collapse

Instance Method Details

#allowlisted?(absolute_path, allowlist) ⇒ Boolean

Returns:

  • (Boolean)

23
24
25
26
27
28
29
# File 'lib/gitlab/utils.rb', line 23

def allowlisted?(absolute_path, allowlist)
  path = absolute_path.downcase

  allowlist.map(&:downcase).any? do |allowed_path|
    path.start_with?(allowed_path)
  end
end

#append_path(host, path) ⇒ Object

Append path to host, making sure there's one single / in between


67
68
69
# File 'lib/gitlab/utils.rb', line 67

def append_path(host, path)
  "#{host.to_s.sub(%r{\/+$}, '')}/#{path.to_s.sub(%r{^\/+}, '')}"
end

#boolean_to_yes_no(bool) ⇒ Object


107
108
109
110
111
112
113
# File 'lib/gitlab/utils.rb', line 107

def boolean_to_yes_no(bool)
  if bool
    'Yes'
  else
    'No'
  end
end

#bytes_to_megabytes(bytes) ⇒ Object


142
143
144
# File 'lib/gitlab/utils.rb', line 142

def bytes_to_megabytes(bytes)
  bytes.to_f / Numeric::MEGABYTE
end

#check_allowed_absolute_path!(path, allowlist) ⇒ Object

Raises:

  • (StandardError)

31
32
33
34
35
36
# File 'lib/gitlab/utils.rb', line 31

def check_allowed_absolute_path!(path, allowlist)
  return unless Pathname.new(path).absolute?
  return if allowlisted?(path, allowlist)

  raise StandardError, "path #{path} is not allowed"
end

#check_path_traversal!(path) ⇒ Object

Ensure that the relative path will not traverse outside the base directory We url decode the path to avoid passing invalid paths forward in url encoded format. Also see gitlab.com/gitlab-org/gitlab/-/merge_requests/24223#note_284122580 It also checks for ALT_SEPARATOR aka '' (forward slash)


12
13
14
15
16
17
18
19
20
21
# File 'lib/gitlab/utils.rb', line 12

def check_path_traversal!(path)
  path = decode_path(path)
  path_regex = /(\A(\.{1,2})\z|\A\.\.[\/\\]|[\/\\]\.\.\z|[\/\\]\.\.[\/\\]|\n)/

  if path.match?(path_regex)
    raise PathTraversalAttackError.new('Invalid path')
  end

  path
end

#decode_path(encoded_path) ⇒ Object


38
39
40
41
42
43
44
45
# File 'lib/gitlab/utils.rb', line 38

def decode_path(encoded_path)
  decoded = CGI.unescape(encoded_path)
  if decoded != CGI.unescape(decoded)
    raise StandardError, "path #{encoded_path} is not allowed"
  end

  decoded
end

#deep_indifferent_access(data) ⇒ Object


158
159
160
161
162
163
164
165
166
# File 'lib/gitlab/utils.rb', line 158

def deep_indifferent_access(data)
  if data.is_a?(Array)
    data.map(&method(:deep_indifferent_access))
  elsif data.is_a?(Hash)
    data.with_indifferent_access
  else
    data
  end
end

#ensure_array_from_string(string_or_array) ⇒ Object

Used in EE Accepts either an Array or a String and returns an array


152
153
154
155
156
# File 'lib/gitlab/utils.rb', line 152

def ensure_array_from_string(string_or_array)
  return string_or_array if string_or_array.is_a?(Array)

  string_or_array.split(',').map(&:strip)
end

#ensure_utf8_size(str, bytes:) ⇒ Object

Raises:

  • (ArgumentError)

51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/gitlab/utils.rb', line 51

def ensure_utf8_size(str, bytes:)
  raise ArgumentError, 'Empty string provided!' if str.empty?
  raise ArgumentError, 'Negative string size provided!' if bytes < 0

  truncated = str.each_char.each_with_object(+'') do |char, object|
    if object.bytesize + char.bytesize > bytes
      break object
    else
      object.concat(char)
    end
  end

  truncated + ('0' * (bytes - truncated.bytesize))
end

#force_utf8(str) ⇒ Object


47
48
49
# File 'lib/gitlab/utils.rb', line 47

def force_utf8(str)
  str.dup.force_encoding(Encoding::UTF_8)
end

#ms_to_round_sec(ms) ⇒ Object


146
147
148
# File 'lib/gitlab/utils.rb', line 146

def ms_to_round_sec(ms)
  (ms.to_f / 1000).round(6)
end

#multiple_key_invert(hash) ⇒ Object

Invert a hash, collecting all keys that map to a given value in an array.

Unlike `Hash#invert`, where the last encountered pair wins, and which has the type `Hash[k, v] => Hash[v, k]`, `multiple_key_invert` does not lose any information, has the type `Hash[k, v] => Hash[v, Array]`, and the original hash can always be reconstructed.

example:

multiple_key_invert({ a: 1, b: 2, c: 1 })
# => { 1 => [:a, :c], 2 => [:b] }

196
197
198
199
200
# File 'lib/gitlab/utils.rb', line 196

def multiple_key_invert(hash)
  hash.flat_map { |k, v| Array.wrap(v).zip([k].cycle) }
    .group_by(&:first)
    .transform_values { |kvs| kvs.map(&:last) }
end

#nlbr(str) ⇒ Object

Converts newlines into HTML line break elements


91
92
93
# File 'lib/gitlab/utils.rb', line 91

def nlbr(str)
  ActionView::Base.full_sanitizer.sanitize(+str, tags: []).gsub(/\r?\n/, '<br>').html_safe
end

#parse_url(uri_string) ⇒ Object

Converts a string to an Addressable::URI object. If the string is not a valid URI, it returns nil. Param uri_string should be a String object. This method returns an Addressable::URI object or nil.


179
180
181
182
# File 'lib/gitlab/utils.rb', line 179

def parse_url(uri_string)
  Addressable::URI.parse(uri_string)
rescue Addressable::URI::InvalidURIError, TypeError
end

#random_stringObject


115
116
117
# File 'lib/gitlab/utils.rb', line 115

def random_string
  Random.rand(Float::MAX.to_i).to_s(36)
end

#remove_line_breaks(str) ⇒ Object


95
96
97
# File 'lib/gitlab/utils.rb', line 95

def remove_line_breaks(str)
  str.gsub(/\r?\n/, '')
end

#slugify(str) ⇒ Object

A slugified version of the string, suitable for inclusion in URLs and domain names. Rules:

* Lowercased
* Anything not matching [a-z0-9-] is replaced with a -
* Maximum length is 63 bytes
* First/Last Character is not a hyphen

78
79
80
81
82
# File 'lib/gitlab/utils.rb', line 78

def slugify(str)
  str.downcase
    .gsub(/[^a-z0-9]/, '-')[0..62]
    .gsub(/(\A-+|-+\z)/, '')
end

#stable_sort_by(list) ⇒ Object

This sort is stable (see en.wikipedia.org/wiki/Sorting_algorithm#Stability) contrary to the bare Ruby sort_by method. Using just sort_by leads to instability across different platforms (e.g., x86_64-linux and x86_64-darwin18) which in turn leads to different sorting results for the equal elements across these platforms. This method uses a list item's original index position to break ties.


208
209
210
# File 'lib/gitlab/utils.rb', line 208

def stable_sort_by(list)
  list.sort_by.with_index { |x, idx| [yield(x), idx] }
end

#string_to_ip_object(str) ⇒ Object


168
169
170
171
172
173
# File 'lib/gitlab/utils.rb', line 168

def string_to_ip_object(str)
  return unless str

  IPAddr.new(str)
rescue IPAddr::InvalidAddressError
end

#to_boolean(value, default: nil) ⇒ Object


99
100
101
102
103
104
105
# File 'lib/gitlab/utils.rb', line 99

def to_boolean(value, default: nil)
  return value if [true, false].include?(value)
  return true if value =~ /^(true|t|yes|y|1|on)$/i
  return false if value =~ /^(false|f|no|n|0|off)$/i

  default
end

#to_exclusive_sentence(array) ⇒ Object

Wraps ActiveSupport's Array#to_sentence to convert the given array to a comma-separated sentence joined with localized 'or' Strings instead of 'and'.


86
87
88
# File 'lib/gitlab/utils.rb', line 86

def to_exclusive_sentence(array)
  array.to_sentence(two_words_connector: _(' or '), last_word_connector: _(', or '))
end

#try_megabytes_to_bytes(size) ⇒ Object


136
137
138
139
140
# File 'lib/gitlab/utils.rb', line 136

def try_megabytes_to_bytes(size)
  Integer(size).megabytes
rescue ArgumentError
  size
end

#which(cmd, env = ENV) ⇒ Object

See: stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby Cross-platform way of finding an executable in the $PATH.

which('ruby') #=> /usr/bin/ruby

123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/gitlab/utils.rb', line 123

def which(cmd, env = ENV)
  exts = env['PATHEXT'] ? env['PATHEXT'].split(';') : ['']

  env['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each do |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable?(exe) && !File.directory?(exe)
    end
  end

  nil
end