├── .gitignore ├── .travis.yml ├── .jshintrc ├── package.json ├── lib └── path-is-inside.js ├── README.md ├── LICENSE.txt └── test └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | script: 5 | npm run lint && npm test 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "globalstrict": true, 7 | "immed": true, 8 | "indent": 4, 9 | "latedef": "nofunc", 10 | "maxlen": 120, 11 | "newcap": true, 12 | "noarg": true, 13 | "node": true, 14 | "nonew": true, 15 | "quotmark": "double", 16 | "trailing": true, 17 | "undef": true, 18 | "unused": true, 19 | "white": true 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "path-is-inside", 3 | "description": "Tests whether one path is inside another path", 4 | "keywords": ["path", "directory", "folder", "inside", "relative"], 5 | "version": "1.0.2", 6 | "author": "Domenic Denicola (https://domenic.me)", 7 | "license": "(WTFPL OR MIT)", 8 | "repository": "domenic/path-is-inside", 9 | "main": "lib/path-is-inside.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "scripts": { 14 | "test": "mocha", 15 | "lint": "jshint lib" 16 | }, 17 | "devDependencies": { 18 | "jshint": "~2.3.0", 19 | "mocha": "~1.15.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/path-is-inside.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | 5 | module.exports = function (thePath, potentialParent) { 6 | // For inside-directory checking, we want to allow trailing slashes, so normalize. 7 | thePath = stripTrailingSep(thePath); 8 | potentialParent = stripTrailingSep(potentialParent); 9 | 10 | // Node treats only Windows as case-insensitive in its path module; we follow those conventions. 11 | if (process.platform === "win32") { 12 | thePath = thePath.toLowerCase(); 13 | potentialParent = potentialParent.toLowerCase(); 14 | } 15 | 16 | return thePath.lastIndexOf(potentialParent, 0) === 0 && 17 | ( 18 | thePath[potentialParent.length] === path.sep || 19 | thePath[potentialParent.length] === undefined 20 | ); 21 | }; 22 | 23 | function stripTrailingSep(thePath) { 24 | if (thePath[thePath.length - 1] === path.sep) { 25 | return thePath.slice(0, -1); 26 | } 27 | return thePath; 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Is This Path Inside This Other Path? 2 | 3 | It turns out this question isn't trivial to answer using Node's built-in path APIs. A naive `indexOf`-based solution will fail sometimes on Windows, which is case-insensitive (see e.g. [isaacs/npm#4214][]). You might then think to be clever with `path.resolve`, but you have to be careful to account for situations whether the paths have different drive letters, or else you'll cause bugs like [isaacs/npm#4313][]. And let's not even get started on trailing slashes. 4 | 5 | The **path-is-inside** package will give you a robust, cross-platform way of detecting whether a given path is inside another path. 6 | 7 | ## Usage 8 | 9 | Pretty simple. First the path being tested; then the potential parent. Like so: 10 | 11 | ```js 12 | var pathIsInside = require("path-is-inside"); 13 | 14 | pathIsInside("/x/y/z", "/x/y") // true 15 | pathIsInside("/x/y", "/x/y/z") // false 16 | ``` 17 | 18 | Paths are considered to be inside themselves: 19 | 20 | ```js 21 | pathIsInside("/x/y", "/x/y"); // true 22 | ``` 23 | 24 | ## OS-Specific Behavior 25 | 26 | Like Node's built-in path module, path-is-inside treats all file paths on Windows as case-insensitive, whereas it treats all file paths on *-nix operating systems as case-sensitive. Keep this in mind especially when working on a Mac, where, despite Node's defaults, the OS usually treats paths case-insensitively. 27 | 28 | In practice, this means: 29 | 30 | ```js 31 | // On Windows 32 | 33 | pathIsInside("C:\\X\\Y\\Z", "C:\\x\\y") // true 34 | 35 | // On *-nix, including Mac OS X 36 | 37 | pathIsInside("/X/Y/Z", "/x/y") // false 38 | ``` 39 | 40 | [isaacs/npm#4214]: https://github.com/isaacs/npm/pull/4214 41 | [isaacs/npm#4313]: https://github.com/isaacs/npm/issues/4313 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Dual licensed under WTFPL and MIT: 2 | 3 | --- 4 | 5 | Copyright © 2013–2016 Domenic Denicola 6 | 7 | This work is free. You can redistribute it and/or modify it under the 8 | terms of the Do What The Fuck You Want To Public License, Version 2, 9 | as published by Sam Hocevar. See below for more details. 10 | 11 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 | Version 2, December 2004 13 | 14 | Copyright (C) 2004 Sam Hocevar 15 | 16 | Everyone is permitted to copy and distribute verbatim or modified 17 | copies of this license document, and changing it is allowed as long 18 | as the name is changed. 19 | 20 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 21 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 22 | 23 | 0. You just DO WHAT THE FUCK YOU WANT TO. 24 | 25 | --- 26 | 27 | The MIT License (MIT) 28 | 29 | Copyright © 2013–2016 Domenic Denicola 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all 39 | copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | SOFTWARE. 48 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var path = require("path"); 5 | var pathIsInside = require(".."); 6 | 7 | // The test data is in the form [thePath, potentialParent, expectedResult]. 8 | 9 | // For *nix-derived operating systems, we will test all these, plus all permutations with `/` appended, which should 10 | // give the same result. 11 | var nixTests = [ 12 | ["/x/y/z", "/a/b/c", false], 13 | ["/x/y/z", "/x/y", true], 14 | ["/x/y/z", "/x/y/z", true], 15 | ["/x/y/z", "/x/y/z/w", false], 16 | ["/x/y/z", "/x/y/w", false], 17 | 18 | ["/x/y", "/x/yy", false], 19 | ["/x/yy", "/x/y", false], 20 | 21 | ["/X/y/z", "/x/y", false], 22 | ["/x/Y/z", "/x/y/z", false] 23 | ]; 24 | 25 | // For Windows, we will test all these, plus all permutations with `\` appended, plus all permutations with the drive 26 | // letter lowercased. 27 | var windowsTests = [ 28 | ["C:\\x\\y\\z", "C:\\a\\b\\c", false], 29 | ["C:\\x\\y\\z", "C:\\x\\y", true], 30 | ["C:\\x\\y\\z", "C:\\x\\y\\z", true], 31 | ["C:\\x\\y\\z", "C:\\x\\y\\z\\w", false], 32 | ["C:\\x\\y\\z", "C:\\x\\y\\w", false], 33 | 34 | ["C:\\x\\y", "C:\\x\\yy", false], 35 | ["C:\\x\\yy", "C:\\x\\y", false], 36 | 37 | ["C:\\x\\y\\z", "D:\\x\\y\\z", false], 38 | ["C:\\x\\y\\z", "D:\\x\\y\\z\\w", false] 39 | ]; 40 | 41 | describe("*-nix style paths", function () { 42 | describe("process.platform = \"darwin\"", function () { 43 | before(function () { 44 | Object.defineProperty(process, "platform", { value: "darwin" }); 45 | Object.defineProperty(path, "sep", { value: "/" }); 46 | }); 47 | 48 | nixTests.forEach(function (data) { 49 | runCase(data); 50 | runCase([data[0], data[1] + "/", data[2]]); 51 | runCase([data[0] + "/", data[1], data[2]]); 52 | runCase([data[0] + "/", data[1] + "/", data[2]]); 53 | }); 54 | }); 55 | 56 | describe("process.platform = \"linux\"", function () { 57 | before(function () { 58 | Object.defineProperty(process, "platform", { value: "linux" }); 59 | Object.defineProperty(path, "sep", { value: "/" }); 60 | }); 61 | 62 | nixTests.forEach(function (data) { 63 | runCase(data); 64 | runCase([data[0], data[1] + "/", data[2]]); 65 | runCase([data[0] + "/", data[1], data[2]]); 66 | runCase([data[0] + "/", data[1] + "/", data[2]]); 67 | }); 68 | }); 69 | }); 70 | 71 | describe("Windows-style paths", function () { 72 | before(function () { 73 | Object.defineProperty(process, "platform", { value: "win32" }); 74 | Object.defineProperty(path, "sep", { value: "\\" }); 75 | }); 76 | 77 | describe("Uppercase drive letters", function () { 78 | windowsTests.forEach(runCases); 79 | }); 80 | describe("Uppercase, then lowercase, drive letters", function () { 81 | windowsTests.forEach(function (data) { 82 | runCases([data[0], data[1].toLowerCase(), data[2]]); 83 | }); 84 | }); 85 | describe("Lowercase, then uppercase, drive letters", function () { 86 | windowsTests.forEach(function (data) { 87 | runCases([data[0], data[1].toLowerCase(), data[2]]); 88 | }); 89 | }); 90 | describe("Lowercase drive letters", function () { 91 | windowsTests.forEach(function (data) { 92 | runCases([data[0].toLowerCase(), data[1].toLowerCase(), data[2]]); 93 | }); 94 | }); 95 | 96 | function runCases(data) { 97 | runCase(data); 98 | runCase([data[0], data[1] + "\\", data[2]]); 99 | runCase([data[0] + "\\", data[1], data[2]]); 100 | runCase([data[0] + "\\", data[1] + "\\", data[2]]); 101 | } 102 | }); 103 | 104 | function runCase(data) { 105 | specify("pathIsInside(\"" + data[0] + "\", \"" + data[1] + "\") should be " + data[2], function () { 106 | assert.strictEqual(pathIsInside(data[0], data[1]), data[2]); 107 | }); 108 | } 109 | --------------------------------------------------------------------------------