class Asciidoctor::PathResolver


=> Start path /etc is outside of jail: /path/to/docs’
end
puts e.message
rescue SecurityError => e
resolver.system_path(‘images’, ‘/etc’, ‘/path/to/docs’)
begin
=> ‘/path/to/docs/images’
resolver.system_path(‘/path/to/docs/images’, nil, ‘/path/to/docs’)
=> ‘path ../../../../../../css refers to location outside jail: /path/to/docs (disallowed in safe mode)’
end
puts e.message
rescue SecurityError => e
resolver.system_path(‘../../../css’, ‘../../..’, ‘/path/to/docs’, :recover => false)
begin
=> ‘C:/data/docs/css’
resolver.system_path(‘..\..\css’, ‘C:\data\docs\assets’, ‘C:\data\docs’)
=> ‘C:/data/docs’
resolver.system_path(‘..’, ‘C:\data\docs\assets’, ‘C:\data\docs’)
=> ‘/path/to/docs/css’
resolver.system_path(‘../../../css’, ‘../../..’, ‘/path/to/docs’)
=> ‘/path/to/docs/css’
resolver.system_path(‘../../../css’, nil, ‘/path/to/docs’)
=> ‘/path/to/docs’
resolver.system_path(‘..’, nil, ‘/path/to/docs’)
=> ‘/path/to/docs’
resolver.system_path(nil, nil, ‘/path/to/docs’)
=> ‘/etc/images’
resolver.system_path(”, ‘/etc/images’)
=> ‘/etc/images’
resolver.system_path(‘images’, ‘/etc’)
=> ‘/etc/images’
resolver.system_path(‘/etc/images’)
=> ‘/path/to/images’
resolver.system_path(‘../images’)
=> ‘/path/to/docs/images’
resolver.system_path(‘images’)
=> ‘/path/to/docs’
resolver.working_dir
# System Paths
=> ‘../assets/images/tiger.png’
resolver.web_path(‘tiger.png’, ‘../assets/images’)
=> ‘assets/images’
resolver.web_path(‘images’, ‘assets’)
=> ‘/images’
resolver.web_path(‘/../images’)
=> ‘./assets/images’
resolver.web_path(‘./images/../assets/images’)
=> ‘/images’
resolver.web_path(‘/images’)
=> ‘./images’
resolver.web_path(‘./images’)
=> ‘images’
resolver.web_path(‘images’)
# Web Paths
resolver = PathResolver.new
Examples
makes the class both deterministic and easier to test.
and windows paths independent of the operating system on which it runs. This
internalizing these operations is that the class is able to handle both posix
it handles all aspects of path manipulation. The main benefit of
This class makes no use of path utilities from the Ruby libraries. Instead,
handles the task of joining a parent (start) and child (target) path.
Since joining two paths can result in an insecure path, this class also
directories outside of a jail root, if specified.
path name. Secure paths are paths which are restricted from accessing
paths are void of duplicate parent and current directory references in the
The main emphasis of the class is on creating clean and secure paths. Clean
system paths.
This class includes operations for handling both web paths (request URIs) and
Public: Handles all operations for resolving, cleaning and joining paths.

def expand_path path

returns a String path with any parent or self references resolved.

path - the String path to expand

was constructed.
in the expanded path is the one specified when the class
absolute if the path is absolute. The file separator used
The result will be relative if the path is relative and

and cleaning self references (.).
Public: Expand the path by resolving any parent references (..)
def expand_path path
  path_segments, path_root, _ = partition_path path
  join_path path_segments, path_root
end

def initialize file_separator = nil, working_dir = nil


working_dir - the String working directory (optional, default: Dir.pwd)
(optional, default: File::SEPARATOR)
file_separator - the String file separator to use for path operations

expanded to an absolute path inside the constructor.
(to override the present working directory). The working directory will be
file separator (to override the system default) and the working directory
Public: Construct a new instance of PathResolver, optionally specifying the
def initialize file_separator = nil, working_dir = nil
  @file_separator = file_separator ? file_separator : (::File::ALT_SEPARATOR || ::File::SEPARATOR)
  if working_dir
    @working_dir = (is_root? working_dir) ? working_dir : (::File.expand_path working_dir)
  else
    @working_dir = ::File.expand_path ::Dir.pwd
  end
  @_partition_path_sys = {}
  @_partition_path_web = {}
end

def is_root? path

returns a Boolean indicating whether the path is an absolute root path

path - the String path to check

This operation correctly handles both posix and windows paths.
Public: Check if the specified path is an absolute root path
def is_root? path
  # Unix absolute paths and UNC paths start with slash
  if path.start_with? SLASH
    true
  # Windows roots can begin with drive letter
  elsif @file_separator == BACKSLASH && WindowsRootRx =~ path
    true
  else
    false
  end
end

def is_unc? path

returns a Boolean indicating whether the path is a UNC path

path - the String path to check

Public: Determine if the path is a UNC (root) path
def is_unc? path
  path.start_with? DOUBLE_SLASH
end

def is_web_root? path

returns a Boolean indicating whether the path is an absolute (root) web path

path - the String path to check

Public: Determine if the path is an absolute (root) web path
def is_web_root? path
  path.start_with? SLASH
end

def join_path segments, root = nil

separator and prepending the root, if specified
returns a String path formed by joining the segments using the posix file

root - a String path root (optional, default: nil)
segments - a String Array of path segments

a relative path.
if specified, to construct an absolute path. Otherwise join the segments as
how to work with paths specified this way, regardless of OS). Use the root,
Public: Join the segments using the posix file separator (since Ruby knows
def join_path segments, root = nil
  if root
    %(#{root}#{segments * SLASH})
  else
    segments * SLASH
  end
end

def partition_path path, web_path = false

QUESTION is it worth it to normalize slashes? it doubles the time elapsed
--
version of the path.
path root (e.g., '/', './', 'c:/') if the path is absolute and the posix
Returns a 3-item Array containing the Array of String path segments, the

as a web path (optional, default: false)
web_path - a Boolean indicating whether the path should be handled
path - the String path to partition

path before being partitioned.
or segments that are self references (.). The path is converted to a posix
Public: Partition the path into path segments and remove any empty segments
def partition_path path, web_path = false
  if (result = web_path ? @_partition_path_web[path] : @_partition_path_sys[path])
    return result
  end
  posix_path = posixfy path
  root = if web_path
    # ex. /sample/path
    if is_web_root? posix_path
      SLASH
    # ex. ./sample/path
    elsif posix_path.start_with? DOT_SLASH
      DOT_SLASH
    # ex. sample/path
    else
      nil
    end
  else
    if is_root? posix_path
      # ex. //sample/path
      if is_unc? posix_path
        DOUBLE_SLASH
      # ex. /sample/path
      elsif posix_path.start_with? SLASH
        SLASH
      # ex. c:/sample/path
      else
        posix_path[0..(posix_path.index SLASH)]
      end
    # ex. ./sample/path
    elsif posix_path.start_with? DOT_SLASH
      DOT_SLASH
    # ex. sample/path
    else
      nil
    end
  end
  path_segments = posix_path.split SLASH
  # shift twice for a UNC path
  if root == DOUBLE_SLASH
    path_segments = path_segments[2..-1]
  # shift once for any other root
  elsif root
    path_segments.shift
  end
  # strip out all dot entries
  path_segments.delete DOT
  # QUESTION should we chomp trailing /? (we pay a small fraction)
  #posix_path = posix_path.chomp '/'
  (web_path ? @_partition_path_web : @_partition_path_sys)[path] = [path_segments, root, posix_path]
end

def posixfy path

returns a String path with any backslashes replaced with forward slashes

path - the String path to normalize

Public: Normalize path by converting any backslashes to forward slashes
def posixfy path
  if path.nil_or_empty?
    ''
  elsif path.include? BACKSLASH
    path.tr BACKSLASH, SLASH
  else
    path
  end
end

def relative_path filename, base_directory

Return the relative path String of the filename calculated from the base directory

base_directory - An absolute base directory as a String
filename - An absolute file name as a String

If either the filename or the base_directory are not absolute paths, no work is done.

Public: Calculate the relative path to this absolute filename from the specified base directory
def relative_path filename, base_directory
  if (is_root? filename) && (is_root? base_directory)
    offset = base_directory.chomp(@file_separator).length + 1
    filename[offset..-1]
  else
    filename
  end
end

def system_path target, start, jail = nil, opts = {}

that the resolved path be contained within the jail, if provided
any parent references resolved and self references removed and enforces
returns a String path that joins the target path with the start path with

* :target_name is used in messages to refer to the path being resolved
when an illegal path is encountered
* :recover is used to control whether the processor should auto-recover
opts - an optional Hash of options to control processing (default: {}):
jail - the String jail path to confine the resolved path
start - the String start (i.e., parent) path
target - the String target path

specified in the constructor.
If the resolved path is relative, resolve it relative to the working_dir
any location on the system. If the resolved path is absolute, use it as is.
the jail path. If a jail path is not provided, the resolved path may be
path is specified, enforce that the resolved directory is contained within
Public: Resolve a system path from the target and start paths. If a jail
def system_path target, start, jail = nil, opts = {}
  recover = opts.fetch :recover, true
  if jail
    unless is_root? jail
      raise ::SecurityError, %(Jail is not an absolute path: #{jail})
    end
    jail = posixfy jail
  end
  if target.nil_or_empty?
    target_segments = []
  else
    target_segments, target_root, _ = partition_path target 
  end
  if target_segments.empty?
    if start.nil_or_empty?
      return jail ? jail : @working_dir
    elsif is_root? start
      unless jail
        return expand_path start
      end
    else
      return system_path start, jail, jail 
    end
  end

  if target_root && target_root != DOT_SLASH
    resolved_target = join_path target_segments, target_root
    # if target is absolute and a sub-directory of jail, or
    # a jail is not in place, let it slide
    if !jail || (resolved_target.start_with? jail)
      return resolved_target
    end
  end

  if start.nil_or_empty?
    start = jail ? jail : @working_dir
  elsif is_root? start
    start = posixfy start
  else
    start = system_path start, jail, jail
  end

  # both jail and start have been posixfied at this point
  if jail == start
    jail_segments, jail_root, _ = partition_path jail
    start_segments = jail_segments.dup
  elsif jail
    unless start.start_with? jail
      raise ::SecurityError, %(#{opts[:target_name] || 'Start path'} #{start} is outside of jail: #{jail} (disallowed in safe mode))
    end
    start_segments, start_root, _ = partition_path start 
    jail_segments, jail_root, _ = partition_path jail

    # Already checked for this condition
    #if start_root != jail_root
    #  raise ::SecurityError, %(Jail root #{jail_root} does not match root of #{opts[:target_name] || 'start path'}: #{start_root})
    #end
  else
    start_segments, start_root, _ = partition_path start
    jail_root = start_root
  end

  resolved_segments = start_segments.dup
  warned = false
  target_segments.each do |segment|
    if segment == DOT_DOT
      if jail
        if resolved_segments.length > jail_segments.length
          resolved_segments.pop
        elsif !recover
          raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode))
        elsif !warned
          warn %(asciidoctor: WARNING: #{opts[:target_name] || 'path'} has illegal reference to ancestor of jail, auto-recovering)
          warned = true
        end
      else
        resolved_segments.pop
      end
    else
      resolved_segments.push segment
    end
  end

  join_path resolved_segments, jail_root
end

def web_path target, start = nil

references removed
start path with any parent references resolved and self
returns a String path that joins the target path with the

start - the String start (i.e., parent) path
target - the String target path

That check should happen before this method is invoked.
The target is assumed to be a path, not a qualified URI.

references and remove any self references.
The main function of this operation is to resolve any parent
Public: Resolve a web path from the target and start paths.
def web_path target, start = nil
  target = posixfy target
  start = posixfy start
  uri_prefix = nil
  unless start.nil_or_empty? || (is_web_root? target)
    target = %(#{start}#{SLASH}#{target})
    if (target.include? ':') && UriSniffRx =~ target
      uri_prefix = $~[0]
      target = target[uri_prefix.length..-1]
    end
  end
  # use this logic instead if we want to normalize target if it contains a URI
  #unless is_web_root? target
  #  if preserve_uri_target && (target.include? ':') && UriSniffRx =~ target
  #    uri_prefix = $~[0]
  #    target = target[uri_prefix.length..-1]
  #  elsif !start.nil_or_empty?
  #    target = %(#{start}#{SLASH}#{target})
  #    if (target.include? ':') && UriSniffRx =~ target
  #      uri_prefix = $~[0]
  #      target = target[uri_prefix.length..-1]
  #    end
  #  end
  #end
  target_segments, target_root, _ = partition_path target, true
  resolved_segments = []
  target_segments.each do |segment|
    if segment == DOT_DOT
      if resolved_segments.empty?
        resolved_segments << segment unless target_root && target_root != DOT_SLASH
      elsif resolved_segments[-1] == DOT_DOT
        resolved_segments << segment
      else
        resolved_segments.pop
      end
    else
      resolved_segments << segment
    end
  end
  if uri_prefix
    %(#{uri_prefix}#{join_path resolved_segments, target_root})
  else
    join_path resolved_segments, target_root
  end
end