Module: OKF::Path

Defined in:
lib/okf/path.rb

Defined Under Namespace

Classes: Error

Class Method Summary collapse

Class Method Details

.join_under!(root, path) ⇒ Object

Raises:



23
24
25
26
27
28
29
30
# File 'lib/okf/path.rb', line 23

def self.join_under!(root, path)
  relative = normalize_relative!(path)
  expanded_root = File.expand_path(root.to_s)
  expanded_path = File.expand_path(File.join(expanded_root, relative))
  raise Error, "path escapes bundle root" unless under?(expanded_root, expanded_path)

  expanded_path
end

.normalize_relative!(path) ⇒ Object

Raises:



8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/okf/path.rb', line 8

def self.normalize_relative!(path)
  value = path.to_s
  raise Error, "path is blank" if value.empty?
  raise Error, "path contains null byte" if value.include?("\0")
  raise Error, "path must be relative" if value.start_with?("/")
  raise Error, "path must use forward slashes" if value.include?("\\")

  parts = value.split("/")
  if parts.any? { |part| part.empty? || part == "." || part == ".." }
    raise Error, "path contains unsafe segment"
  end

  parts.join("/")
end

.under?(root, path) ⇒ Boolean

Is path the root itself or a descendant of it? Pure string containment (no disk access), so it works on both lexical paths (File.expand_path) and symlink-resolved ones (File.realpath) — the shell resolves, this decides. Both arguments must already be absolute and normalized the same way.

The prefix guards against a sibling passing as a child ("/foo" is not under "/food"), and reuses the root itself as the prefix when the root already ends in the separator — i.e. the filesystem root "/", whose children would otherwise be tested against "//" and every one rejected.

Returns:

  • (Boolean)


41
42
43
44
45
46
# File 'lib/okf/path.rb', line 41

def self.under?(root, path)
  return true if path == root

  prefix = root.end_with?(File::SEPARATOR) ? root : "#{root}#{File::SEPARATOR}"
  path.start_with?(prefix)
end