2018-10-22 03:00:50 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-11-06 06:07:16 -05:00
|
|
|
module Gitlab
|
|
|
|
module Utils
|
|
|
|
extend self
|
2020-05-25 08:08:23 -04:00
|
|
|
PathTraversalAttackError ||= Class.new(StandardError)
|
2014-11-06 06:07:16 -05:00
|
|
|
|
2018-12-04 10:59:01 -05:00
|
|
|
# Ensure that the relative path will not traverse outside the base directory
|
2020-02-26 16:09:11 -05:00
|
|
|
# We url decode the path to avoid passing invalid paths forward in url encoded format.
|
|
|
|
# We are ok to pass some double encoded paths to File.open since they won't resolve.
|
|
|
|
# Also see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24223#note_284122580
|
|
|
|
# It also checks for ALT_SEPARATOR aka '\' (forward slash)
|
|
|
|
def check_path_traversal!(path, allowed_absolute: false)
|
|
|
|
path = CGI.unescape(path)
|
|
|
|
|
|
|
|
if path.start_with?("..#{File::SEPARATOR}", "..#{File::ALT_SEPARATOR}") ||
|
2018-12-04 10:59:01 -05:00
|
|
|
path.include?("#{File::SEPARATOR}..#{File::SEPARATOR}") ||
|
2020-02-26 16:09:11 -05:00
|
|
|
path.end_with?("#{File::SEPARATOR}..") ||
|
|
|
|
(!allowed_absolute && Pathname.new(path).absolute?)
|
|
|
|
|
2020-05-25 08:08:23 -04:00
|
|
|
raise PathTraversalAttackError.new('Invalid path')
|
2020-02-26 16:09:11 -05:00
|
|
|
end
|
2018-12-04 10:59:01 -05:00
|
|
|
|
|
|
|
path
|
|
|
|
end
|
|
|
|
|
2015-05-02 17:43:46 -04:00
|
|
|
def force_utf8(str)
|
2019-07-25 05:33:32 -04:00
|
|
|
str.dup.force_encoding(Encoding::UTF_8)
|
2015-05-02 17:43:46 -04:00
|
|
|
end
|
2016-10-28 16:56:13 -04:00
|
|
|
|
2018-11-22 09:35:49 -05:00
|
|
|
def ensure_utf8_size(str, bytes:)
|
2018-11-27 08:34:05 -05:00
|
|
|
raise ArgumentError, 'Empty string provided!' if str.empty?
|
|
|
|
raise ArgumentError, 'Negative string size provided!' if bytes.negative?
|
2018-11-22 09:35:49 -05:00
|
|
|
|
2018-11-23 04:03:43 -05:00
|
|
|
truncated = str.each_char.each_with_object(+'') do |char, object|
|
2018-11-22 09:35:49 -05:00
|
|
|
if object.bytesize + char.bytesize > bytes
|
|
|
|
break object
|
|
|
|
else
|
|
|
|
object.concat(char)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-11-28 06:00:17 -05:00
|
|
|
truncated + ('0' * (bytes - truncated.bytesize))
|
2018-11-22 09:35:49 -05:00
|
|
|
end
|
|
|
|
|
2018-11-06 18:54:55 -05:00
|
|
|
# Append path to host, making sure there's one single / in between
|
|
|
|
def append_path(host, path)
|
|
|
|
"#{host.to_s.sub(%r{\/+$}, '')}/#{path.to_s.sub(%r{^\/+}, '')}"
|
|
|
|
end
|
|
|
|
|
2017-08-07 14:00:11 -04:00
|
|
|
# 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
|
|
|
|
def slugify(str)
|
|
|
|
return str.downcase
|
|
|
|
.gsub(/[^a-z0-9]/, '-')[0..62]
|
|
|
|
.gsub(/(\A-+|-+\z)/, '')
|
|
|
|
end
|
|
|
|
|
2020-01-07 19:07:43 -05:00
|
|
|
# Wraps ActiveSupport's Array#to_sentence to convert the given array to a
|
|
|
|
# comma-separated sentence joined with localized 'or' Strings instead of 'and'.
|
|
|
|
def to_exclusive_sentence(array)
|
|
|
|
array.to_sentence(two_words_connector: _(' or '), last_word_connector: _(', or '))
|
|
|
|
end
|
|
|
|
|
2018-04-04 13:11:55 -04:00
|
|
|
# Converts newlines into HTML line break elements
|
|
|
|
def nlbr(str)
|
2018-12-10 10:08:42 -05:00
|
|
|
ActionView::Base.full_sanitizer.sanitize(+str, tags: []).gsub(/\r?\n/, '<br>').html_safe
|
2018-04-04 13:11:55 -04:00
|
|
|
end
|
|
|
|
|
2018-01-05 16:36:18 -05:00
|
|
|
def remove_line_breaks(str)
|
|
|
|
str.gsub(/\r?\n/, '')
|
|
|
|
end
|
|
|
|
|
2020-05-14 11:08:14 -04:00
|
|
|
def to_boolean(value, default: nil)
|
2016-10-28 16:56:13 -04:00
|
|
|
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
|
|
|
|
|
2020-05-14 11:08:14 -04:00
|
|
|
default
|
2016-10-28 16:56:13 -04:00
|
|
|
end
|
2017-05-25 07:31:21 -04:00
|
|
|
|
|
|
|
def boolean_to_yes_no(bool)
|
|
|
|
if bool
|
|
|
|
'Yes'
|
|
|
|
else
|
|
|
|
'No'
|
|
|
|
end
|
|
|
|
end
|
2017-07-20 11:32:17 -04:00
|
|
|
|
|
|
|
def random_string
|
|
|
|
Random.rand(Float::MAX.to_i).to_s(36)
|
|
|
|
end
|
2017-11-08 15:53:10 -05:00
|
|
|
|
|
|
|
# See: http://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
|
|
|
|
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
|
2018-03-06 16:16:55 -05:00
|
|
|
|
2019-03-12 09:04:05 -04:00
|
|
|
def try_megabytes_to_bytes(size)
|
|
|
|
Integer(size).megabytes
|
|
|
|
rescue ArgumentError
|
|
|
|
size
|
|
|
|
end
|
|
|
|
|
2018-04-11 23:05:07 -04:00
|
|
|
def bytes_to_megabytes(bytes)
|
|
|
|
bytes.to_f / Numeric::MEGABYTE
|
|
|
|
end
|
|
|
|
|
2020-04-21 11:21:10 -04:00
|
|
|
def ms_to_round_sec(ms)
|
2020-04-27 23:09:53 -04:00
|
|
|
(ms.to_f / 1000).round(6)
|
2020-04-21 11:21:10 -04:00
|
|
|
end
|
|
|
|
|
2018-03-06 16:16:55 -05:00
|
|
|
# Used in EE
|
|
|
|
# Accepts either an Array or a String and returns an array
|
|
|
|
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
|
2018-09-02 10:35:15 -04:00
|
|
|
|
|
|
|
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
|
2019-07-24 13:59:38 -04:00
|
|
|
|
|
|
|
def string_to_ip_object(str)
|
|
|
|
return unless str
|
|
|
|
|
|
|
|
IPAddr.new(str)
|
|
|
|
rescue IPAddr::InvalidAddressError
|
|
|
|
end
|
2020-03-04 16:07:54 -05:00
|
|
|
|
|
|
|
# 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.
|
|
|
|
def parse_url(uri_string)
|
|
|
|
Addressable::URI.parse(uri_string)
|
|
|
|
rescue Addressable::URI::InvalidURIError, TypeError
|
|
|
|
end
|
2014-11-06 06:07:16 -05:00
|
|
|
end
|
|
|
|
end
|