Class: Asciidoctor::PathResolver
- Inherits:
-
Object
- Object
- Asciidoctor::PathResolver
- Includes:
- Logging
- Defined in:
- lib/asciidoctor/path_resolver.rb
Overview
> start path /etc is outside of jail: /path/to/docs’
Constant Summary collapse
- DOT =
'.'
- DOT_DOT =
'..'
- DOT_SLASH =
'./'
- SLASH =
'/'
- BACKSLASH =
'\\'
- DOUBLE_SLASH =
'//'
- WindowsRootRx =
/^(?:[a-zA-Z]:)?[\\\/]/
Instance Attribute Summary collapse
Instance Method Summary collapse
-
#absolute_path?(path) ⇒ Boolean
(also: #root?)
Check whether the specified path is an absolute path.
-
#descends_from?(path, base) ⇒ Boolean
Determine whether path descends from base.
-
#expand_path(path) ⇒ Object
Expand the specified path by converting the path to a posix path, resolving parent references (..), and removing self references (.).
-
#initialize(file_separator = nil, working_dir = nil) ⇒ PathResolver
constructor
Construct a new instance of PathResolver, optionally specifying the file separator (to override the system default) and the working directory (to override the present working directory).
-
#join_path(segments, root = nil) ⇒ Object
Join the segments using the posix file separator (since Ruby knows how to work with paths specified this way, regardless of OS).
-
#partition_path(path, web = nil) ⇒ Object
Partition the path into path segments and remove self references (.) and the trailing slash, if present.
-
#posixify(path) ⇒ Object
(also: #posixfy)
Normalize path by converting any backslashes to forward slashes.
-
#relative_path(path, base) ⇒ String
Calculate the relative path to this absolute path from the specified base directory.
-
#system_path(target, start = nil, jail = nil, opts = {}) ⇒ Object
Securely resolve a system path.
-
#unc?(path) ⇒ Boolean
Determine if the path is a UNC (root) path.
-
#web_path(target, start = nil) ⇒ Object
Resolve a web path from the target and start paths.
-
#web_root?(path) ⇒ Boolean
Determine if the path is an absolute (root) web path.
Methods included from Logging
#logger, #message_with_context
Constructor Details
#initialize(file_separator = nil, working_dir = nil) ⇒ PathResolver
Construct a new instance of PathResolver, optionally specifying the file separator (to override the system default) and the working directory (to override the present working directory). The working directory will be expanded to an absolute path inside the constructor.
126 127 128 129 130 131 |
# File 'lib/asciidoctor/path_resolver.rb', line 126 def initialize file_separator = nil, working_dir = nil @file_separator = file_separator || ::File::ALT_SEPARATOR || ::File::SEPARATOR @working_dir = working_dir ? ((root? working_dir) ? (posixify working_dir) : (::File. working_dir)) : ::Dir.pwd @_partition_path_sys = {} @_partition_path_web = {} end |
Instance Attribute Details
#file_separator ⇒ Object
114 115 116 |
# File 'lib/asciidoctor/path_resolver.rb', line 114 def file_separator @file_separator end |
#working_dir ⇒ Object
115 116 117 |
# File 'lib/asciidoctor/path_resolver.rb', line 115 def working_dir @working_dir end |
Instance Method Details
#absolute_path?(path) ⇒ Boolean Also known as: root?
Check whether the specified path is an absolute path.
This operation considers both posix paths and Windows paths. The path does not have to be posixified beforehand. This operation does not handle URIs.
Unix absolute paths start with a slash. UNC paths can start with a slash or backslash. Windows roots can start with a drive letter.
144 145 146 |
# File 'lib/asciidoctor/path_resolver.rb', line 144 def absolute_path? path (path.start_with? SLASH) || (@file_separator == BACKSLASH && (WindowsRootRx.match? path)) end |
#descends_from?(path, base) ⇒ Boolean
Determine whether path descends from base.
If path equals base, or base is a parent of path, return true.
197 198 199 200 201 202 203 204 205 |
# File 'lib/asciidoctor/path_resolver.rb', line 197 def descends_from? path, base if base == path 0 elsif base == SLASH (path.start_with? SLASH) && 1 else (path.start_with? base + SLASH) && (base.length + 1) end end |
#expand_path(path) ⇒ Object
Expand the specified path by converting the path to a posix path, resolving parent references (..), and removing self references (.).
254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/asciidoctor/path_resolver.rb', line 254 def path path_segments, path_root = partition_path path if path.include? DOT_DOT resolved_segments = [] path_segments.each do |segment| segment == DOT_DOT ? resolved_segments.pop : resolved_segments << segment end join_path resolved_segments, path_root else join_path path_segments, path_root end end |
#join_path(segments, root = nil) ⇒ Object
Join the segments using the posix file separator (since Ruby knows how to work with paths specified this way, regardless of OS). Use the root, if specified, to construct an absolute path. Otherwise join the segments as a relative path.
328 329 330 |
# File 'lib/asciidoctor/path_resolver.rb', line 328 def join_path segments, root = nil root ? %(#{root}#{segments.join SLASH}) : (segments.join SLASH) end |
#partition_path(path, web = nil) ⇒ Object
Partition the path into path segments and remove self references (.) and the trailing slash, if present. Prior to being partitioned, the path is converted to a posix path.
Parent references are not resolved by this method since the consumer often needs to handle this resolution in a certain context (checking for the breach of a jail, for instance).
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/asciidoctor/path_resolver.rb', line 279 def partition_path path, web = nil if (result = (cache = web ? @_partition_path_web : @_partition_path_sys)[path]) return result end posix_path = posixify path if web # ex. /sample/path if web_root? posix_path root = SLASH # ex. ./sample/path elsif posix_path.start_with? DOT_SLASH root = DOT_SLASH # else ex. sample/path end elsif root? posix_path # ex. //sample/path if unc? posix_path root = DOUBLE_SLASH # ex. /sample/path elsif posix_path.start_with? SLASH root = SLASH # ex. C:/sample/path (or file:///sample/path in browser environment) else root = posix_path.slice 0, (posix_path.index SLASH) + 1 end # ex. ./sample/path elsif posix_path.start_with? DOT_SLASH root = DOT_SLASH # else ex. sample/path end path_segments = (root ? (posix_path.slice root.length, posix_path.length) : posix_path).split SLASH # strip out all dot entries path_segments.delete DOT cache[path] = [path_segments, root] end |
#posixify(path) ⇒ Object Also known as: posixfy
Normalize path by converting any backslashes to forward slashes
238 239 240 241 242 243 244 |
# File 'lib/asciidoctor/path_resolver.rb', line 238 def posixify path if path @file_separator == BACKSLASH && (path.include? BACKSLASH) ? (path.tr BACKSLASH, SLASH) : path else '' end end |
#relative_path(path, base) ⇒ String
Calculate the relative path to this absolute path from the specified base directory
If neither path or base are absolute paths, the path is not contained within the base directory, or the relative path cannot be computed, the original path is returned work is done.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/asciidoctor/path_resolver.rb', line 217 def relative_path path, base if root? path if (offset = descends_from? path, base) path.slice offset, path.length else begin (Pathname.new path).relative_path_from(Pathname.new base).to_s rescue path end end else path end end |
#system_path(target, start = nil, jail = nil, opts = {}) ⇒ Object
Securely resolve a system path
Resolve a system path from the target relative to the start path, jail path, or working directory (specified in the constructor), in that order. If a jail path is specified, enforce that the resolved path descends from the jail path. If a jail path is not provided, the resolved path may be any location on the system. If the resolved path is absolute, use it as is (unless it breaches the jail path). Expand all parent and self references in the resolved path.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/asciidoctor/path_resolver.rb', line 352 def system_path target, start = nil, jail = nil, opts = {} if jail raise ::SecurityError, %(Jail is not an absolute path: #{jail}) unless root? jail #raise ::SecurityError, %(Jail is not a canonical path: #{jail}) if jail.include? DOT_DOT jail = posixify jail end if target if root? target target_path = target if jail && !(descends_from? target_path, jail) if opts.fetch :recover, true logger.warn %(#{opts[:target_name] || 'path'} is outside of jail; recovering automatically) target_segments, _ = partition_path target_path jail_segments, jail_root = partition_path jail return join_path jail_segments + target_segments, jail_root else raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} is outside of jail: #{jail} (disallowed in safe mode)) end end return target_path else target_segments, _ = partition_path target end else target_segments = [] end if target_segments.empty? if start.nil_or_empty? return jail || @working_dir elsif root? start if jail start = posixify start else return start end else target_segments, _ = partition_path start start = jail || @working_dir end elsif start.nil_or_empty? start = jail || @working_dir elsif root? start start = posixify start if jail else #start = system_path start, jail, jail, opts start = %(#{(jail || @working_dir).chomp '/'}/#{start}) end # both jail and start have been posixified at this point if jail is set if jail && (recheck = !(descends_from? start, jail)) && @file_separator == BACKSLASH start_segments, start_root = partition_path start jail_segments, jail_root = partition_path jail if start_root != jail_root if opts.fetch :recover, true logger.warn %(start path for #{opts[:target_name] || 'path'} is outside of jail root; recovering automatically) start_segments = jail_segments recheck = false else raise ::SecurityError, %(start path for #{opts[:target_name] || 'path'} #{start} refers to location outside jail root: #{jail} (disallowed in safe mode)) end end else start_segments, jail_root = partition_path start end if (resolved_segments = start_segments + target_segments).include? DOT_DOT unresolved_segments, resolved_segments = resolved_segments, [] if jail jail_segments, _ = partition_path jail unless jail_segments warned = false unresolved_segments.each do |segment| if segment == DOT_DOT if resolved_segments.size > jail_segments.size resolved_segments.pop elsif opts.fetch :recover, true unless warned logger.warn %(#{opts[:target_name] || 'path'} has illegal reference to ancestor of jail; recovering automatically) warned = true end else raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode)) end else resolved_segments << segment end end else unresolved_segments.each do |segment| segment == DOT_DOT ? resolved_segments.pop : resolved_segments << segment end end end if recheck target_path = join_path resolved_segments, jail_root if descends_from? target_path, jail target_path elsif opts.fetch :recover, true logger.warn %(#{opts[:target_name] || 'path'} is outside of jail; recovering automatically) jail_segments, _ = partition_path jail unless jail_segments join_path jail_segments + target_segments, jail_root else raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} is outside of jail: #{jail} (disallowed in safe mode)) end else join_path resolved_segments, jail_root end end |
#unc?(path) ⇒ Boolean
Determine if the path is a UNC (root) path
176 177 178 |
# File 'lib/asciidoctor/path_resolver.rb', line 176 def unc? path path.start_with? DOUBLE_SLASH end |
#web_path(target, start = nil) ⇒ Object
Resolve a web path from the target and start paths. The main function of this operation is to resolve any parent references and remove any self references.
The target is assumed to be a path, not a qualified URI. That check should happen before this method is invoked.
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 |
# File 'lib/asciidoctor/path_resolver.rb', line 476 def web_path target, start = nil target = posixify target start = posixify start unless start.nil_or_empty? || (web_root? target) target, uri_prefix = extract_uri_prefix %(#{start}#{(start.end_with? SLASH) ? '' : SLASH}#{target}) end # use this logic instead if we want to normalize target if it contains a URI #unless web_root? target # target, uri_prefix = extract_uri_prefix target if preserve_uri_target # target, uri_prefix = extract_uri_prefix %(#{start}#{SLASH}#{target}) unless uri_prefix || start.nil_or_empty? #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 # checking for empty would eliminate repeating forward slashes #resolved_segments << segment unless segment.empty? end end if (resolved_path = join_path resolved_segments, target_root).include? ' ' resolved_path = resolved_path.gsub ' ', '%20' end uri_prefix ? %(#{uri_prefix}#{resolved_path}) : resolved_path end |
#web_root?(path) ⇒ Boolean
Determine if the path is an absolute (root) web path
185 186 187 |
# File 'lib/asciidoctor/path_resolver.rb', line 185 def web_root? path path.start_with? SLASH end |