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 |