| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library path.context; | 5 import 'dart:math' as math; |
| 6 | 6 |
| 7 import 'characters.dart' as chars; | 7 import 'characters.dart' as chars; |
| 8 import 'internal_style.dart'; | 8 import 'internal_style.dart'; |
| 9 import 'style.dart'; | 9 import 'style.dart'; |
| 10 import 'parsed_path.dart'; | 10 import 'parsed_path.dart'; |
| 11 import 'path_exception.dart'; | 11 import 'path_exception.dart'; |
| 12 import '../path.dart' as p; | 12 import '../path.dart' as p; |
| 13 | 13 |
| 14 Context createInternal() => new Context._internal(); | 14 Context createInternal() => new Context._internal(); |
| 15 | 15 |
| (...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 238 String joinAll(Iterable<String> parts) { | 238 String joinAll(Iterable<String> parts) { |
| 239 var buffer = new StringBuffer(); | 239 var buffer = new StringBuffer(); |
| 240 var needsSeparator = false; | 240 var needsSeparator = false; |
| 241 var isAbsoluteAndNotRootRelative = false; | 241 var isAbsoluteAndNotRootRelative = false; |
| 242 | 242 |
| 243 for (var part in parts.where((part) => part != '')) { | 243 for (var part in parts.where((part) => part != '')) { |
| 244 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { | 244 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { |
| 245 // If the new part is root-relative, it preserves the previous root but | 245 // If the new part is root-relative, it preserves the previous root but |
| 246 // replaces the path after it. | 246 // replaces the path after it. |
| 247 var parsed = _parse(part); | 247 var parsed = _parse(part); |
| 248 parsed.root = this.rootPrefix(buffer.toString()); | 248 var path = buffer.toString(); |
| 249 parsed.root = path.substring( |
| 250 0, style.rootLength(path, withDrive: true)); |
| 249 if (style.needsSeparator(parsed.root)) { | 251 if (style.needsSeparator(parsed.root)) { |
| 250 parsed.separators[0] = style.separator; | 252 parsed.separators[0] = style.separator; |
| 251 } | 253 } |
| 252 buffer.clear(); | 254 buffer.clear(); |
| 253 buffer.write(parsed.toString()); | 255 buffer.write(parsed.toString()); |
| 254 } else if (this.isAbsolute(part)) { | 256 } else if (this.isAbsolute(part)) { |
| 255 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); | 257 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); |
| 256 // An absolute path discards everything before it. | 258 // An absolute path discards everything before it. |
| 257 buffer.clear(); | 259 buffer.clear(); |
| 258 buffer.write(part); | 260 buffer.write(part); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 293 /// // Windows | 295 /// // Windows |
| 294 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] | 296 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] |
| 295 List<String> split(String path) { | 297 List<String> split(String path) { |
| 296 var parsed = _parse(path); | 298 var parsed = _parse(path); |
| 297 // Filter out empty parts that exist due to multiple separators in a row. | 299 // Filter out empty parts that exist due to multiple separators in a row. |
| 298 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); | 300 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); |
| 299 if (parsed.root != null) parsed.parts.insert(0, parsed.root); | 301 if (parsed.root != null) parsed.parts.insert(0, parsed.root); |
| 300 return parsed.parts; | 302 return parsed.parts; |
| 301 } | 303 } |
| 302 | 304 |
| 305 /// Canonicalizes [path]. |
| 306 /// |
| 307 /// This is guaranteed to return the same path for two different input paths |
| 308 /// if and only if both input paths point to the same location. Unlike |
| 309 /// [normalize], it returns absolute paths when possible and canonicalizes |
| 310 /// ASCII case on Windows. |
| 311 /// |
| 312 /// Note that this does not resolve symlinks. |
| 313 /// |
| 314 /// If you want a map that uses path keys, it's probably more efficient to |
| 315 /// pass [equals] and [hash] to [new HashMap] than it is to canonicalize every |
| 316 /// key. |
| 317 String canonicalize(String path) { |
| 318 path = absolute(path); |
| 319 if (style != Style.windows && !_needsNormalization(path)) return path; |
| 320 |
| 321 var parsed = _parse(path); |
| 322 parsed.normalize(canonicalize: true); |
| 323 return parsed.toString(); |
| 324 } |
| 325 |
| 303 /// Normalizes [path], simplifying it by handling `..`, and `.`, and | 326 /// Normalizes [path], simplifying it by handling `..`, and `.`, and |
| 304 /// removing redundant path separators whenever possible. | 327 /// removing redundant path separators whenever possible. |
| 305 /// | 328 /// |
| 329 /// Note that this is *not* guaranteed to return the same result for two |
| 330 /// equivalent input paths. For that, see [canonicalize]. Or, if you're using |
| 331 /// paths as map keys, pass [equals] and [hash] to [new HashMap]. |
| 332 /// |
| 306 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' | 333 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' |
| 307 String normalize(String path) { | 334 String normalize(String path) { |
| 308 if (!_needsNormalization(path)) return path; | 335 if (!_needsNormalization(path)) return path; |
| 309 | 336 |
| 310 var parsed = _parse(path); | 337 var parsed = _parse(path); |
| 311 parsed.normalize(); | 338 parsed.normalize(); |
| 312 return parsed.toString(); | 339 return parsed.toString(); |
| 313 } | 340 } |
| 314 | 341 |
| 315 /// Returns whether [path] needs to be normalized. | 342 /// Returns whether [path] needs to be normalized. |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 | 390 |
| 364 // Empty paths are normalized to ".". | 391 // Empty paths are normalized to ".". |
| 365 if (previous == null) return true; | 392 if (previous == null) return true; |
| 366 | 393 |
| 367 // Trailing separators are removed. | 394 // Trailing separators are removed. |
| 368 if (style.isSeparator(previous)) return true; | 395 if (style.isSeparator(previous)) return true; |
| 369 | 396 |
| 370 // Single dots and double dots are normalized to directory traversals. | 397 // Single dots and double dots are normalized to directory traversals. |
| 371 if (previous == chars.PERIOD && | 398 if (previous == chars.PERIOD && |
| 372 (previousPrevious == null || | 399 (previousPrevious == null || |
| 373 previousPrevious == chars.SLASH || | 400 style.isSeparator(previousPrevious) || |
| 374 previousPrevious == chars.PERIOD)) { | 401 previousPrevious == chars.PERIOD)) { |
| 375 return true; | 402 return true; |
| 376 } | 403 } |
| 377 | 404 |
| 378 return false; | 405 return false; |
| 379 } | 406 } |
| 380 | 407 |
| 381 /// Attempts to convert [path] to an equivalent relative path relative to | 408 /// Attempts to convert [path] to an equivalent relative path relative to |
| 382 /// [root]. | 409 /// [root]. |
| 383 /// | 410 /// |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 439 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { | 466 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { |
| 440 return pathParsed.toString(); | 467 return pathParsed.toString(); |
| 441 } | 468 } |
| 442 | 469 |
| 443 // If the root prefixes don't match (for example, different drive letters | 470 // If the root prefixes don't match (for example, different drive letters |
| 444 // on Windows), then there is no relative path, so just return the absolute | 471 // on Windows), then there is no relative path, so just return the absolute |
| 445 // one. In Windows, drive letters are case-insenstive and we allow | 472 // one. In Windows, drive letters are case-insenstive and we allow |
| 446 // calculation of relative paths, even if a path has not been normalized. | 473 // calculation of relative paths, even if a path has not been normalized. |
| 447 if (fromParsed.root != pathParsed.root && | 474 if (fromParsed.root != pathParsed.root && |
| 448 ((fromParsed.root == null || pathParsed.root == null) || | 475 ((fromParsed.root == null || pathParsed.root == null) || |
| 449 fromParsed.root.toLowerCase().replaceAll('/', '\\') != | 476 !style.pathsEqual(fromParsed.root, pathParsed.root))) { |
| 450 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { | |
| 451 return pathParsed.toString(); | 477 return pathParsed.toString(); |
| 452 } | 478 } |
| 453 | 479 |
| 454 // Strip off their common prefix. | 480 // Strip off their common prefix. |
| 455 while (fromParsed.parts.length > 0 && | 481 while (fromParsed.parts.length > 0 && |
| 456 pathParsed.parts.length > 0 && | 482 pathParsed.parts.length > 0 && |
| 457 fromParsed.parts[0] == pathParsed.parts[0]) { | 483 style.pathsEqual(fromParsed.parts[0], pathParsed.parts[0])) { |
| 458 fromParsed.parts.removeAt(0); | 484 fromParsed.parts.removeAt(0); |
| 459 fromParsed.separators.removeAt(1); | 485 fromParsed.separators.removeAt(1); |
| 460 pathParsed.parts.removeAt(0); | 486 pathParsed.parts.removeAt(0); |
| 461 pathParsed.separators.removeAt(1); | 487 pathParsed.separators.removeAt(1); |
| 462 } | 488 } |
| 463 | 489 |
| 464 // If there are any directories left in the from path, we need to walk up | 490 // If there are any directories left in the from path, we need to walk up |
| 465 // out of them. If a directory left in the from path is '..', it cannot | 491 // out of them. If a directory left in the from path is '..', it cannot |
| 466 // be cancelled by adding a '..'. | 492 // be cancelled by adding a '..'. |
| 467 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { | 493 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 492 | 518 |
| 493 return pathParsed.toString(); | 519 return pathParsed.toString(); |
| 494 } | 520 } |
| 495 | 521 |
| 496 /// Returns `true` if [child] is a path beneath `parent`, and `false` | 522 /// Returns `true` if [child] is a path beneath `parent`, and `false` |
| 497 /// otherwise. | 523 /// otherwise. |
| 498 /// | 524 /// |
| 499 /// path.isWithin('/root/path', '/root/path/a'); // -> true | 525 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
| 500 /// path.isWithin('/root/path', '/root/other'); // -> false | 526 /// path.isWithin('/root/path', '/root/other'); // -> false |
| 501 /// path.isWithin('/root/path', '/root/path'); // -> false | 527 /// path.isWithin('/root/path', '/root/path'); // -> false |
| 502 bool isWithin(String parent, String child) { | 528 bool isWithin(String parent, String child) => |
| 529 _isWithinOrEquals(parent, child) == _PathRelation.within; |
| 530 |
| 531 /// Returns `true` if [path1] points to the same location as [path2], and |
| 532 /// `false` otherwise. |
| 533 /// |
| 534 /// The [hash] function returns a hash code that matches these equality |
| 535 /// semantics. |
| 536 bool equals(String path1, String path2) => |
| 537 _isWithinOrEquals(path1, path2) == _PathRelation.equal; |
| 538 |
| 539 /// Compares two paths and returns an enum value indicating their relationship |
| 540 /// to one another. |
| 541 /// |
| 542 /// This never returns [_PathRelation.inconclusive]. |
| 543 _PathRelation _isWithinOrEquals(String parent, String child) { |
| 503 // Make both paths the same level of relative. We're only able to do the | 544 // Make both paths the same level of relative. We're only able to do the |
| 504 // quick comparison if both paths are in the same format, and making a path | 545 // quick comparison if both paths are in the same format, and making a path |
| 505 // absolute is faster than making it relative. | 546 // absolute is faster than making it relative. |
| 506 var parentIsAbsolute = isAbsolute(parent); | 547 var parentIsAbsolute = isAbsolute(parent); |
| 507 var childIsAbsolute = isAbsolute(child); | 548 var childIsAbsolute = isAbsolute(child); |
| 508 if (parentIsAbsolute && !childIsAbsolute) { | 549 if (parentIsAbsolute && !childIsAbsolute) { |
| 509 child = absolute(child); | 550 child = absolute(child); |
| 510 if (style.isRootRelative(parent)) parent = absolute(parent); | 551 if (style.isRootRelative(parent)) parent = absolute(parent); |
| 511 } else if (childIsAbsolute && !parentIsAbsolute) { | 552 } else if (childIsAbsolute && !parentIsAbsolute) { |
| 512 parent = absolute(parent); | 553 parent = absolute(parent); |
| 513 if (style.isRootRelative(child)) child = absolute(child); | 554 if (style.isRootRelative(child)) child = absolute(child); |
| 514 } else if (childIsAbsolute && parentIsAbsolute) { | 555 } else if (childIsAbsolute && parentIsAbsolute) { |
| 515 var childIsRootRelative = style.isRootRelative(child); | 556 var childIsRootRelative = style.isRootRelative(child); |
| 516 var parentIsRootRelative = style.isRootRelative(parent); | 557 var parentIsRootRelative = style.isRootRelative(parent); |
| 517 | 558 |
| 518 if (childIsRootRelative && !parentIsRootRelative) { | 559 if (childIsRootRelative && !parentIsRootRelative) { |
| 519 child = absolute(child); | 560 child = absolute(child); |
| 520 } else if (parentIsRootRelative && !childIsRootRelative) { | 561 } else if (parentIsRootRelative && !childIsRootRelative) { |
| 521 parent = absolute(parent); | 562 parent = absolute(parent); |
| 522 } | 563 } |
| 523 } | 564 } |
| 524 | 565 |
| 525 var fastResult = _isWithinFast(parent, child); | 566 var result = _isWithinOrEqualsFast(parent, child); |
| 526 if (fastResult != null) return fastResult; | 567 if (result != _PathRelation.inconclusive) return result; |
| 527 | 568 |
| 528 var relative; | 569 var relative; |
| 529 try { | 570 try { |
| 530 relative = this.relative(child, from: parent); | 571 relative = this.relative(child, from: parent); |
| 531 } on PathException catch (_) { | 572 } on PathException catch (_) { |
| 532 // If no relative path from [parent] to [child] is found, [child] | 573 // If no relative path from [parent] to [child] is found, [child] |
| 533 // definitely isn't a child of [parent]. | 574 // definitely isn't a child of [parent]. |
| 534 return false; | 575 return _PathRelation.different; |
| 535 } | 576 } |
| 536 | 577 |
| 537 var parts = this.split(relative); | 578 if (!this.isRelative(relative)) return _PathRelation.different; |
| 538 return this.isRelative(relative) && | 579 if (relative == '.') return _PathRelation.equal; |
| 539 parts.first != '..' && | 580 if (relative == '..') return _PathRelation.different; |
| 540 parts.first != '.'; | 581 return (relative.length >= 3 && |
| 582 relative.startsWith('..') && |
| 583 style.isSeparator(relative.codeUnitAt(2))) |
| 584 ? _PathRelation.different |
| 585 : _PathRelation.within; |
| 541 } | 586 } |
| 542 | 587 |
| 543 /// An optimized implementation of [isWithin] that doesn't handle a few | 588 /// An optimized implementation of [_isWithinOrEquals] that doesn't handle a |
| 544 /// complex cases. | 589 /// few complex cases. |
| 545 bool _isWithinFast(String parent, String child) { | 590 _PathRelation _isWithinOrEqualsFast(String parent, String child) { |
| 546 // Normally we just bail when we see "." path components, but we can handle | 591 // Normally we just bail when we see "." path components, but we can handle |
| 547 // a single dot easily enough. | 592 // a single dot easily enough. |
| 548 if (parent == '.') parent = ''; | 593 if (parent == '.') parent = ''; |
| 549 | 594 |
| 550 var parentRootLength = style.rootLength(parent); | 595 var parentRootLength = style.rootLength(parent); |
| 551 var childRootLength = style.rootLength(child); | 596 var childRootLength = style.rootLength(child); |
| 552 | 597 |
| 553 // If the roots aren't the same length, we know both paths are absolute or | 598 // If the roots aren't the same length, we know both paths are absolute or |
| 554 // both are root-relative, and thus that the roots are meaningfully | 599 // both are root-relative, and thus that the roots are meaningfully |
| 555 // different. | 600 // different. |
| 556 // | 601 // |
| 557 // isWithin("C:/bar", "//foo/bar/baz") //=> false | 602 // isWithin("C:/bar", "//foo/bar/baz") //=> false |
| 558 // isWithin("http://example.com/", "http://google.com/bar") //=> false | 603 // isWithin("http://example.com/", "http://google.com/bar") //=> false |
| 559 if (parentRootLength != childRootLength) return false; | 604 if (parentRootLength != childRootLength) return _PathRelation.different; |
| 560 | |
| 561 var parentCodeUnits = parent.codeUnits; | |
| 562 var childCodeUnits = child.codeUnits; | |
| 563 | 605 |
| 564 // Make sure that the roots are textually the same as well. | 606 // Make sure that the roots are textually the same as well. |
| 565 // | 607 // |
| 566 // isWithin("C:/bar", "D:/bar/baz") //=> false | 608 // isWithin("C:/bar", "D:/bar/baz") //=> false |
| 567 // isWithin("http://example.com/", "http://example.org/bar") //=> false | 609 // isWithin("http://example.com/", "http://example.org/bar") //=> false |
| 568 for (var i = 0; i < parentRootLength; i++) { | 610 for (var i = 0; i < parentRootLength; i++) { |
| 569 var parentCodeUnit = parentCodeUnits[i]; | 611 var parentCodeUnit = parent.codeUnitAt(i); |
| 570 var childCodeUnit = childCodeUnits[i]; | 612 var childCodeUnit = child.codeUnitAt(i); |
| 571 if (parentCodeUnit == childCodeUnit) continue; | 613 if (!style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) { |
| 572 | 614 return _PathRelation.different; |
| 573 // If both code units are separators, that's fine too. | |
| 574 // | |
| 575 // isWithin("C:/", r"C:\foo") //=> true | |
| 576 if (!style.isSeparator(parentCodeUnit) || | |
| 577 !style.isSeparator(childCodeUnit)) { | |
| 578 return false; | |
| 579 } | 615 } |
| 580 } | 616 } |
| 581 | 617 |
| 582 // Start by considering the last code unit as a separator, since | 618 // Start by considering the last code unit as a separator, since |
| 583 // semantically we're starting at a new path component even if we're | 619 // semantically we're starting at a new path component even if we're |
| 584 // comparing relative paths. | 620 // comparing relative paths. |
| 585 var lastCodeUnit = chars.SLASH; | 621 var lastCodeUnit = chars.SLASH; |
| 586 | 622 |
| 623 /// The index of the last separator in [parent]. |
| 624 int lastParentSeparator; |
| 625 |
| 587 // Iterate through both paths as long as they're semantically identical. | 626 // Iterate through both paths as long as they're semantically identical. |
| 588 var parentIndex = parentRootLength; | 627 var parentIndex = parentRootLength; |
| 589 var childIndex = childRootLength; | 628 var childIndex = childRootLength; |
| 590 while (parentIndex < parent.length && childIndex < child.length) { | 629 while (parentIndex < parent.length && childIndex < child.length) { |
| 591 var parentCodeUnit = parentCodeUnits[parentIndex]; | 630 var parentCodeUnit = parent.codeUnitAt(parentIndex); |
| 592 var childCodeUnit = childCodeUnits[childIndex]; | 631 var childCodeUnit = child.codeUnitAt(childIndex); |
| 593 if (parentCodeUnit == childCodeUnit) { | 632 if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) { |
| 633 if (style.isSeparator(parentCodeUnit)) { |
| 634 lastParentSeparator = parentIndex; |
| 635 } |
| 636 |
| 594 lastCodeUnit = parentCodeUnit; | 637 lastCodeUnit = parentCodeUnit; |
| 595 parentIndex++; | 638 parentIndex++; |
| 596 childIndex++; | 639 childIndex++; |
| 597 continue; | |
| 598 } | |
| 599 | |
| 600 // Different separators are considered identical. | |
| 601 var parentIsSeparator = style.isSeparator(parentCodeUnit); | |
| 602 var childIsSeparator = style.isSeparator(childCodeUnit); | |
| 603 if (parentIsSeparator && childIsSeparator) { | |
| 604 lastCodeUnit = parentCodeUnit; | |
| 605 parentIndex++; | |
| 606 childIndex++; | |
| 607 continue; | 640 continue; |
| 608 } | 641 } |
| 609 | 642 |
| 610 // Ignore multiple separators in a row. | 643 // Ignore multiple separators in a row. |
| 611 if (parentIsSeparator && style.isSeparator(lastCodeUnit)) { | 644 if (style.isSeparator(parentCodeUnit) && |
| 645 style.isSeparator(lastCodeUnit)) { |
| 646 lastParentSeparator = parentIndex; |
| 612 parentIndex++; | 647 parentIndex++; |
| 613 continue; | 648 continue; |
| 614 } else if (childIsSeparator && style.isSeparator(lastCodeUnit)) { | 649 } else if (style.isSeparator(childCodeUnit) && |
| 650 style.isSeparator(lastCodeUnit)) { |
| 615 childIndex++; | 651 childIndex++; |
| 616 continue; | 652 continue; |
| 617 } | 653 } |
| 618 | 654 |
| 619 if (parentCodeUnit == chars.PERIOD) { | 655 // If a dot comes after a separator, it may be a directory traversal |
| 620 // If a dot comes after a separator, it may be a directory traversal | 656 // operator. To check that, we need to know if it's followed by either |
| 621 // operator. To check that, we need to know if it's followed by either | 657 // "/" or "./". Otherwise, it's just a normal non-matching character. |
| 622 // "/" or "./". Otherwise, it's just a normal non-matching character. | 658 // |
| 623 // | 659 // isWithin("foo/./bar", "foo/bar/baz") //=> true |
| 624 // isWithin("foo/./bar", "foo/bar/baz") //=> true | 660 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false |
| 625 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false | 661 if (parentCodeUnit == chars.PERIOD && style.isSeparator(lastCodeUnit)) { |
| 626 if (style.isSeparator(lastCodeUnit)) { | 662 parentIndex++; |
| 663 |
| 664 // We've hit "/." at the end of the parent path, which we can ignore, |
| 665 // since the paths were equivalent up to this point. |
| 666 if (parentIndex == parent.length) break; |
| 667 parentCodeUnit = parent.codeUnitAt(parentIndex); |
| 668 |
| 669 // We've hit "/./", which we can ignore. |
| 670 if (style.isSeparator(parentCodeUnit)) { |
| 671 lastParentSeparator = parentIndex; |
| 627 parentIndex++; | 672 parentIndex++; |
| 673 continue; |
| 674 } |
| 628 | 675 |
| 629 // We've hit "/." at the end of the parent path, which we can ignore, | 676 // We've hit "/..", which may be a directory traversal operator that |
| 630 // since the paths were equivalent up to this point. | 677 // we can't handle on the fast track. |
| 631 if (parentIndex == parent.length) break; | 678 if (parentCodeUnit == chars.PERIOD) { |
| 632 parentCodeUnit = parentCodeUnits[parentIndex]; | 679 parentIndex++; |
| 633 | 680 if (parentIndex == parent.length || |
| 634 // We've hit "/./", which we can ignore. | 681 style.isSeparator(parent.codeUnitAt(parentIndex))) { |
| 635 if (style.isSeparator(parentCodeUnit)) { | 682 return _PathRelation.inconclusive; |
| 636 parentIndex++; | |
| 637 continue; | |
| 638 } | |
| 639 | |
| 640 // We've hit "/..", which may be a directory traversal operator that | |
| 641 // we can't handle on the fast track. | |
| 642 if (parentCodeUnit == chars.PERIOD) { | |
| 643 parentIndex++; | |
| 644 if (parentIndex == parent.length || | |
| 645 style.isSeparator(parentCodeUnits[parentIndex])) { | |
| 646 return null; | |
| 647 } | |
| 648 } | 683 } |
| 649 } | 684 } |
| 650 | 685 |
| 651 // If this isn't a directory traversal, fall through so we hit the | 686 // If this isn't a directory traversal, fall through so we hit the |
| 652 // normal handling for mismatched paths. | 687 // normal handling for mismatched paths. |
| 653 } | 688 } |
| 654 | 689 |
| 655 // This is the same logic as above, but for the child path instead of the | 690 // This is the same logic as above, but for the child path instead of the |
| 656 // parent. | 691 // parent. |
| 657 if (childCodeUnit == chars.PERIOD) { | 692 if (childCodeUnit == chars.PERIOD && style.isSeparator(lastCodeUnit)) { |
| 658 if (style.isSeparator(lastCodeUnit)) { | 693 childIndex++; |
| 694 if (childIndex == child.length) break; |
| 695 childCodeUnit = child.codeUnitAt(childIndex); |
| 696 |
| 697 if (style.isSeparator(childCodeUnit)) { |
| 659 childIndex++; | 698 childIndex++; |
| 660 if (childIndex == child.length) break; | 699 continue; |
| 661 childCodeUnit = childCodeUnits[childIndex]; | 700 } |
| 662 | 701 |
| 663 if (style.isSeparator(childCodeUnit)) { | 702 if (childCodeUnit == chars.PERIOD) { |
| 664 childIndex++; | 703 childIndex++; |
| 665 continue; | 704 if (childIndex == child.length || |
| 666 } | 705 style.isSeparator(child.codeUnitAt(childIndex))) { |
| 667 | 706 return _PathRelation.inconclusive; |
| 668 if (childCodeUnit == chars.PERIOD) { | |
| 669 childIndex++; | |
| 670 if (childIndex == child.length || | |
| 671 style.isSeparator(childCodeUnits[childIndex])) { | |
| 672 return null; | |
| 673 } | |
| 674 } | 707 } |
| 675 } | 708 } |
| 676 } | 709 } |
| 677 | 710 |
| 678 // If we're here, we've hit two non-matching, non-significant characters. | 711 // If we're here, we've hit two non-matching, non-significant characters. |
| 679 // As long as the remainders of the two paths don't have any unresolved | 712 // As long as the remainders of the two paths don't have any unresolved |
| 680 // ".." components, we can be confident that [child] is not within | 713 // ".." components, we can be confident that [child] is not within |
| 681 // [parent]. | 714 // [parent]. |
| 682 var childDirection = _pathDirection(childCodeUnits, childIndex); | 715 var childDirection = _pathDirection(child, childIndex); |
| 683 if (childDirection != _PathDirection.belowRoot) return null; | 716 if (childDirection != _PathDirection.belowRoot) { |
| 684 var parentDirection = _pathDirection(parentCodeUnits, parentIndex); | 717 return _PathRelation.inconclusive; |
| 685 if (parentDirection != _PathDirection.belowRoot) return null; | 718 } |
| 686 | 719 |
| 687 return false; | 720 var parentDirection = _pathDirection(parent, parentIndex); |
| 721 if (parentDirection != _PathDirection.belowRoot) { |
| 722 return _PathRelation.inconclusive; |
| 723 } |
| 724 |
| 725 return _PathRelation.different; |
| 688 } | 726 } |
| 689 | 727 |
| 690 // If the child is shorter than the parent, it's probably not within the | 728 // If the child is shorter than the parent, it's probably not within the |
| 691 // parent. The only exception is if the parent has some weird ".." stuff | 729 // parent. The only exception is if the parent has some weird ".." stuff |
| 692 // going on, in which case we do the slow check. | 730 // going on, in which case we do the slow check. |
| 693 // | 731 // |
| 694 // isWithin("foo/bar/baz", "foo/bar") //=> false | 732 // isWithin("foo/bar/baz", "foo/bar") //=> false |
| 695 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true | 733 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true |
| 696 if (childIndex == child.length) { | 734 if (childIndex == child.length) { |
| 697 var direction = _pathDirection(parentCodeUnits, parentIndex); | 735 if (parentIndex == parent.length || |
| 698 return direction == _PathDirection.aboveRoot ? null : false; | 736 style.isSeparator(parent.codeUnitAt(parentIndex))) { |
| 737 lastParentSeparator = parentIndex; |
| 738 } else { |
| 739 lastParentSeparator ??= math.max(0, parentRootLength - 1); |
| 740 } |
| 741 |
| 742 var direction = _pathDirection(parent, |
| 743 lastParentSeparator ?? parentRootLength - 1); |
| 744 if (direction == _PathDirection.atRoot) return _PathRelation.equal; |
| 745 return direction == _PathDirection.aboveRoot |
| 746 ? _PathRelation.inconclusive |
| 747 : _PathRelation.different; |
| 699 } | 748 } |
| 700 | 749 |
| 701 // We've reached the end of the parent path, which means it's time to make a | 750 // We've reached the end of the parent path, which means it's time to make a |
| 702 // decision. Before we do, though, we'll check the rest of the child to see | 751 // decision. Before we do, though, we'll check the rest of the child to see |
| 703 // what that tells us. | 752 // what that tells us. |
| 704 var direction = _pathDirection(childCodeUnits, childIndex); | 753 var direction = _pathDirection(child, childIndex); |
| 705 | 754 |
| 706 // If there are no more components in the child, then it's the same as | 755 // If there are no more components in the child, then it's the same as |
| 707 // the parent, not within it. | 756 // the parent. |
| 708 // | 757 // |
| 709 // isWithin("foo/bar", "foo/bar") //=> false | 758 // isWithin("foo/bar", "foo/bar") //=> false |
| 710 // isWithin("foo/bar", "foo/bar//") //=> false | 759 // isWithin("foo/bar", "foo/bar//") //=> false |
| 711 if (direction == _PathDirection.atRoot) return false; | 760 // equals("foo/bar", "foo/bar") //=> true |
| 761 // equals("foo/bar", "foo/bar//") //=> true |
| 762 if (direction == _PathDirection.atRoot) return _PathRelation.equal; |
| 712 | 763 |
| 713 // If there are unresolved ".." components in the child, no decision we make | 764 // If there are unresolved ".." components in the child, no decision we make |
| 714 // will be valid. We'll abort and do the slow check instead. | 765 // will be valid. We'll abort and do the slow check instead. |
| 715 // | 766 // |
| 716 // isWithin("foo/bar", "foo/bar/..") //=> false | 767 // isWithin("foo/bar", "foo/bar/..") //=> false |
| 717 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false | 768 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false |
| 718 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true | 769 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true |
| 719 if (direction == _PathDirection.aboveRoot) return null; | 770 if (direction == _PathDirection.aboveRoot) { |
| 771 return _PathRelation.inconclusive; |
| 772 } |
| 720 | 773 |
| 721 // The child is within the parent if and only if we're on a separator | 774 // The child is within the parent if and only if we're on a separator |
| 722 // boundary. | 775 // boundary. |
| 723 // | 776 // |
| 724 // isWithin("foo/bar", "foo/bar/baz") //=> true | 777 // isWithin("foo/bar", "foo/bar/baz") //=> true |
| 725 // isWithin("foo/bar/", "foo/bar/baz") //=> true | 778 // isWithin("foo/bar/", "foo/bar/baz") //=> true |
| 726 // isWithin("foo/bar", "foo/barbaz") //=> false | 779 // isWithin("foo/bar", "foo/barbaz") //=> false |
| 727 return style.isSeparator(childCodeUnits[childIndex]) || | 780 return (style.isSeparator(child.codeUnitAt(childIndex)) || |
| 728 style.isSeparator(lastCodeUnit); | 781 style.isSeparator(lastCodeUnit)) |
| 782 ? _PathRelation.within |
| 783 : _PathRelation.different; |
| 729 } | 784 } |
| 730 | 785 |
| 731 // Returns a [_PathDirection] describing the path represented by [codeUnits] | 786 // Returns a [_PathDirection] describing the path represented by [codeUnits] |
| 732 // after [index]. | 787 // starting at [index]. |
| 733 // | 788 // |
| 734 // This ignores leading separators. | 789 // This ignores leading separators. |
| 735 // | 790 // |
| 736 // pathDirection("foo") //=> below root | 791 // pathDirection("foo") //=> below root |
| 737 // pathDirection("foo/bar/../baz") //=> below root | 792 // pathDirection("foo/bar/../baz") //=> below root |
| 738 // pathDirection("//foo/bar/baz") //=> below root | 793 // pathDirection("//foo/bar/baz") //=> below root |
| 739 // pathDirection("/") //=> at root | 794 // pathDirection("/") //=> at root |
| 740 // pathDirection("foo/..") //=> at root | 795 // pathDirection("foo/..") //=> at root |
| 741 // pathDirection("foo/../baz") //=> reaches root | 796 // pathDirection("foo/../baz") //=> reaches root |
| 742 // pathDirection("foo/../..") //=> above root | 797 // pathDirection("foo/../..") //=> above root |
| 743 // pathDirection("foo/../../foo/bar/baz") //=> above root | 798 // pathDirection("foo/../../foo/bar/baz") //=> above root |
| 744 _PathDirection _pathDirection(List<int> codeUnits, int index) { | 799 _PathDirection _pathDirection(String path, int index) { |
| 745 var depth = 0; | 800 var depth = 0; |
| 746 var reachedRoot = false; | 801 var reachedRoot = false; |
| 747 var i = index; | 802 var i = index; |
| 748 while (i < codeUnits.length) { | 803 while (i < path.length) { |
| 749 // Ignore initial separators or doubled separators. | 804 // Ignore initial separators or doubled separators. |
| 750 while (i < codeUnits.length && style.isSeparator(codeUnits[i])) { | 805 while (i < path.length && style.isSeparator(path.codeUnitAt(i))) { |
| 751 i++; | 806 i++; |
| 752 } | 807 } |
| 753 | 808 |
| 754 // If we're at the end, stop. | 809 // If we're at the end, stop. |
| 755 if (i == codeUnits.length) break; | 810 if (i == path.length) break; |
| 756 | 811 |
| 757 // Move through the path component to the next separator. | 812 // Move through the path component to the next separator. |
| 758 var start = i; | 813 var start = i; |
| 759 while (i < codeUnits.length && !style.isSeparator(codeUnits[i])) { | 814 while (i < path.length && !style.isSeparator(path.codeUnitAt(i))) { |
| 760 i++; | 815 i++; |
| 761 } | 816 } |
| 762 | 817 |
| 763 // See if the path component is ".", "..", or a name. | 818 // See if the path component is ".", "..", or a name. |
| 764 if (i - start == 1 && codeUnits[start] == chars.PERIOD) { | 819 if (i - start == 1 && path.codeUnitAt(start) == chars.PERIOD) { |
| 765 // Don't change the depth. | 820 // Don't change the depth. |
| 766 } else if (i - start == 2 && | 821 } else if (i - start == 2 && |
| 767 codeUnits[start] == chars.PERIOD && | 822 path.codeUnitAt(start) == chars.PERIOD && |
| 768 codeUnits[start + 1] == chars.PERIOD) { | 823 path.codeUnitAt(start + 1) == chars.PERIOD) { |
| 769 // ".." backs out a directory. | 824 // ".." backs out a directory. |
| 770 depth--; | 825 depth--; |
| 771 | 826 |
| 772 // If we work back beyond the root, stop. | 827 // If we work back beyond the root, stop. |
| 773 if (depth < 0) break; | 828 if (depth < 0) break; |
| 774 | 829 |
| 775 // Record that we reached the root so we don't return | 830 // Record that we reached the root so we don't return |
| 776 // [_PathDirection.belowRoot]. | 831 // [_PathDirection.belowRoot]. |
| 777 if (depth == 0) reachedRoot = true; | 832 if (depth == 0) reachedRoot = true; |
| 778 } else { | 833 } else { |
| 779 // Step inside a directory. | 834 // Step inside a directory. |
| 780 depth++; | 835 depth++; |
| 781 } | 836 } |
| 782 | 837 |
| 783 // If we're at the end, stop. | 838 // If we're at the end, stop. |
| 784 if (i == codeUnits.length) break; | 839 if (i == path.length) break; |
| 785 | 840 |
| 786 // Move past the separator. | 841 // Move past the separator. |
| 787 i++; | 842 i++; |
| 788 } | 843 } |
| 789 | 844 |
| 790 if (depth < 0) return _PathDirection.aboveRoot; | 845 if (depth < 0) return _PathDirection.aboveRoot; |
| 791 if (depth == 0) return _PathDirection.atRoot; | 846 if (depth == 0) return _PathDirection.atRoot; |
| 792 if (reachedRoot) return _PathDirection.reachesRoot; | 847 if (reachedRoot) return _PathDirection.reachesRoot; |
| 793 return _PathDirection.belowRoot; | 848 return _PathDirection.belowRoot; |
| 794 } | 849 } |
| 795 | 850 |
| 851 /// Returns a hash code for [path] that matches the semantics of [equals]. |
| 852 /// |
| 853 /// Note that the same path may have different hash codes in different |
| 854 /// [Context]s. |
| 855 int hash(String path) { |
| 856 // Make [path] absolute to ensure that equivalent relative and absolute |
| 857 // paths have the same hash code. |
| 858 path = absolute(path); |
| 859 |
| 860 var result = _hashFast(path); |
| 861 if (result != null) return result; |
| 862 |
| 863 var parsed = _parse(path); |
| 864 parsed.normalize(); |
| 865 return _hashFast(parsed.toString()); |
| 866 } |
| 867 |
| 868 /// An optimized implementation of [hash] that doesn't handle internal `..` |
| 869 /// components. |
| 870 /// |
| 871 /// This will handle `..` components that appear at the beginning of the path. |
| 872 int _hashFast(String path) { |
| 873 var hash = 4603; |
| 874 var beginning = true; |
| 875 var wasSeparator = true; |
| 876 for (var i = 0; i < path.length; i++) { |
| 877 var codeUnit = style.canonicalizeCodeUnit(path.codeUnitAt(i)); |
| 878 |
| 879 // Take advantage of the fact that collisions are allowed to ignore |
| 880 // separators entirely. This lets us avoid worrying about cases like |
| 881 // multiple trailing slashes. |
| 882 if (style.isSeparator(codeUnit)) { |
| 883 wasSeparator = true; |
| 884 continue; |
| 885 } |
| 886 |
| 887 if (codeUnit == chars.PERIOD && wasSeparator) { |
| 888 // If a dot comes after a separator, it may be a directory traversal |
| 889 // operator. To check that, we need to know if it's followed by either |
| 890 // "/" or "./". Otherwise, it's just a normal character. |
| 891 // |
| 892 // hash("foo/./bar") == hash("foo/bar") |
| 893 |
| 894 // We've hit "/." at the end of the path, which we can ignore. |
| 895 if (i + 1 == path.length) break; |
| 896 |
| 897 var next = path.codeUnitAt(i + 1); |
| 898 |
| 899 // We can just ignore "/./", since they don't affect the semantics of |
| 900 // the path. |
| 901 if (style.isSeparator(next)) continue; |
| 902 |
| 903 // If the path ends with "/.." or contains "/../", we need to |
| 904 // canonicalize it before we can hash it. We make an exception for ".."s |
| 905 // at the beginning of the path, since those may appear even in a |
| 906 // canonicalized path. |
| 907 if (!beginning && |
| 908 next == chars.PERIOD && |
| 909 (i + 2 == path.length || |
| 910 style.isSeparator(path.codeUnitAt(i + 2)))) { |
| 911 return null; |
| 912 } |
| 913 } |
| 914 |
| 915 // Make sure [hash] stays under 32 bits even after multiplication. |
| 916 hash &= 0x3FFFFFF; |
| 917 hash *= 33; |
| 918 hash ^= codeUnit; |
| 919 wasSeparator = false; |
| 920 beginning = false; |
| 921 } |
| 922 return hash; |
| 923 } |
| 924 |
| 796 /// Removes a trailing extension from the last part of [path]. | 925 /// Removes a trailing extension from the last part of [path]. |
| 797 /// | 926 /// |
| 798 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | 927 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
| 799 String withoutExtension(String path) { | 928 String withoutExtension(String path) { |
| 800 var parsed = _parse(path); | 929 var parsed = _parse(path); |
| 801 | 930 |
| 802 for (var i = parsed.parts.length - 1; i >= 0; i--) { | 931 for (var i = parsed.parts.length - 1; i >= 0; i--) { |
| 803 if (!parsed.parts[i].isEmpty) { | 932 if (!parsed.parts[i].isEmpty) { |
| 804 parsed.parts[i] = parsed.basenameWithoutExtension; | 933 parsed.parts[i] = parsed.basenameWithoutExtension; |
| 805 break; | 934 break; |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 945 | 1074 |
| 946 /// The path never reaches to or above its original root. | 1075 /// The path never reaches to or above its original root. |
| 947 static const belowRoot = const _PathDirection("below root"); | 1076 static const belowRoot = const _PathDirection("below root"); |
| 948 | 1077 |
| 949 final String name; | 1078 final String name; |
| 950 | 1079 |
| 951 const _PathDirection(this.name); | 1080 const _PathDirection(this.name); |
| 952 | 1081 |
| 953 String toString() => name; | 1082 String toString() => name; |
| 954 } | 1083 } |
| 1084 |
| 1085 /// An enum of possible return values for [Context._isWithinOrEquals]. |
| 1086 class _PathRelation { |
| 1087 /// The first path is a proper parent of the second. |
| 1088 /// |
| 1089 /// For example, `foo` is a proper parent of `foo/bar`, but not of `foo`. |
| 1090 static const within = const _PathRelation("within"); |
| 1091 |
| 1092 /// The two paths are equivalent. |
| 1093 /// |
| 1094 /// For example, `foo//bar` is equivalent to `foo/bar`. |
| 1095 static const equal = const _PathRelation("equal"); |
| 1096 |
| 1097 /// The first path is neither a parent of nor equal to the second. |
| 1098 static const different = const _PathRelation("different"); |
| 1099 |
| 1100 /// We couldn't quickly determine any information about the paths' |
| 1101 /// relationship to each other. |
| 1102 /// |
| 1103 /// Only returned by [Context._isWithinOrEqualsFast]. |
| 1104 static const inconclusive = const _PathRelation("inconclusive"); |
| 1105 |
| 1106 final String name; |
| 1107 |
| 1108 const _PathRelation(this.name); |
| 1109 |
| 1110 String toString() => name; |
| 1111 } |
| 1112 |
| OLD | NEW |