├── .gitignore ├── tsconfig.json ├── test ├── test.ts └── tests.json ├── .editorconfig ├── .github └── workflows │ └── test.yml ├── LICENSE ├── package.json ├── src └── index.ts ├── README.md └── .eslintrc.js /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /yarn.lock 3 | /node_modules 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "CommonJS", 5 | "target": "es2015", 6 | "esModuleInterop": true, 7 | "lib": ["esnext"], 8 | "outDir": "dist", 9 | "sourceMap": true, 10 | "declaration": true, 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src/**/*", "test/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import { sep } from 'path'; 2 | import assert from 'assert'; 3 | import uri2path from '../src'; 4 | import _tests from './tests.json'; 5 | 6 | const tests = _tests as { [input: string]: string }; 7 | 8 | describe('file-uri-to-path', function() { 9 | for (const uri of Object.keys(tests)) { 10 | // The test cases were generated from Windows' `PathCreateFromUrlA()` 11 | // function. On Unix, we have to replace the path separator with the 12 | // Unix one instead of the Windows one. 13 | const expected = tests[uri].replace(/\\/g, sep); 14 | 15 | it( 16 | `should convert ${ 17 | JSON.stringify(uri) 18 | } to ${ 19 | JSON.stringify(expected)}`, 20 | function() { 21 | const actual = uri2path(uri); 22 | assert.equal(actual, expected); 23 | } 24 | ); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "file://host/path": "\\\\host\\path", 3 | "file://localhost/etc/fstab": "\\etc\\fstab", 4 | "file:///etc/fstab": "\\etc\\fstab", 5 | "file:///c:/WINDOWS/clock.avi": "c:\\WINDOWS\\clock.avi", 6 | "file://localhost/c|/WINDOWS/clock.avi": "c:\\WINDOWS\\clock.avi", 7 | "file:///c|/WINDOWS/clock.avi": "c:\\WINDOWS\\clock.avi", 8 | "file://localhost/c:/WINDOWS/clock.avi": "c:\\WINDOWS\\clock.avi", 9 | "file://hostname/path/to/the%20file.txt": "\\\\hostname\\path\\to\\the file.txt", 10 | "file:///c:/path/to/the%20file.txt": "c:\\path\\to\\the file.txt", 11 | "file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc": "C:\\Documents and Settings\\davris\\FileSchemeURIs.doc", 12 | "file:///C:/caf%C3%A9/%C3%A5r/d%C3%BCnn/%E7%89%9B%E9%93%83/Ph%E1%BB%9F/%F0%9F%98%B5.exe": "C:\\café\\år\\dünn\\牛铃\\Phở\\😵.exe" 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | tab_width = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [{*.json,*.json.example,*.gyp,*.yml,*.yaml,*.workflow}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [{*.py,*.asm}] 17 | indent_style = space 18 | 19 | [*.py] 20 | indent_size = 4 21 | 22 | [*.asm] 23 | indent_size = 8 24 | 25 | [*.md] 26 | trim_trailing_whitespace = false 27 | 28 | # Ideal settings - some plugins might support these. 29 | [*.js] 30 | quote_type = single 31 | 32 | [{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}] 33 | curly_bracket_next_line = false 34 | spaces_around_operators = true 35 | spaces_around_brackets = outside 36 | # close enough to 1TB 37 | indent_brace_style = K&R 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Test Node.js ${{ matrix.node-version }} on ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | node-version: [6.x, 8.x, 10.x, 12.x] 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Print Node.js Version 25 | run: node --version 26 | 27 | - name: Install Dependencies 28 | run: npm install 29 | env: 30 | CI: true 31 | 32 | - name: Run "build" step 33 | run: npm run build --if-present 34 | env: 35 | CI: true 36 | 37 | - name: Run tests 38 | run: npm test 39 | env: 40 | CI: true 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Nathan Rajlich 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-uri-to-path", 3 | "version": "2.0.0", 4 | "description": "Convert a file: URI to a file path", 5 | "main": "dist/src/index", 6 | "typings": "dist/src/index", 7 | "files": [ 8 | "dist/src" 9 | ], 10 | "scripts": { 11 | "prebuild": "rimraf dist", 12 | "build": "tsc", 13 | "postbuild": "cpy --parents src test '!**/*.ts' dist", 14 | "test": "mocha --reporter spec dist/test/*.js", 15 | "test-lint": "eslint src --ext .js,.ts", 16 | "prepublishOnly": "npm run build" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/TooTallNate/file-uri-to-path.git" 21 | }, 22 | "engines": { 23 | "node": ">= 6" 24 | }, 25 | "keywords": [ 26 | "file", 27 | "uri", 28 | "convert", 29 | "path" 30 | ], 31 | "author": "Nathan Rajlich (http://n8.io/)", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/TooTallNate/file-uri-to-path/issues" 35 | }, 36 | "homepage": "https://github.com/TooTallNate/file-uri-to-path", 37 | "devDependencies": { 38 | "@types/mocha": "^5.2.7", 39 | "@types/node": "^10.5.3", 40 | "@typescript-eslint/eslint-plugin": "1.6.0", 41 | "@typescript-eslint/parser": "1.1.0", 42 | "cpy-cli": "^2.0.0", 43 | "eslint": "5.16.0", 44 | "eslint-config-airbnb": "17.1.0", 45 | "eslint-config-prettier": "4.1.0", 46 | "eslint-import-resolver-typescript": "1.1.1", 47 | "eslint-plugin-import": "2.16.0", 48 | "eslint-plugin-jsx-a11y": "6.2.1", 49 | "eslint-plugin-react": "7.12.4", 50 | "mocha": "^6.2.0", 51 | "rimraf": "^3.0.0", 52 | "typescript": "^3.5.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { sep } from 'path'; 2 | 3 | /** 4 | * File URI to Path function. 5 | * 6 | * @param {String} uri 7 | * @return {String} path 8 | * @api public 9 | */ 10 | 11 | function fileUriToPath(uri: string): string { 12 | if ( 13 | typeof uri !== 'string' || 14 | uri.length <= 7 || 15 | uri.substring(0, 7) !== 'file://' 16 | ) { 17 | throw new TypeError( 18 | 'must pass in a file:// URI to convert to a file path' 19 | ); 20 | } 21 | 22 | const rest = decodeURI(uri.substring(7)); 23 | const firstSlash = rest.indexOf('/'); 24 | let host = rest.substring(0, firstSlash); 25 | let path = rest.substring(firstSlash + 1); 26 | 27 | // 2. Scheme Definition 28 | // As a special case, can be the string "localhost" or the empty 29 | // string; this is interpreted as "the machine from which the URL is 30 | // being interpreted". 31 | if (host === 'localhost') { 32 | host = ''; 33 | } 34 | 35 | if (host) { 36 | host = sep + sep + host; 37 | } 38 | 39 | // 3.2 Drives, drive letters, mount points, file system root 40 | // Drive letters are mapped into the top of a file URI in various ways, 41 | // depending on the implementation; some applications substitute 42 | // vertical bar ("|") for the colon after the drive letter, yielding 43 | // "file:///c|/tmp/test.txt". In some cases, the colon is left 44 | // unchanged, as in "file:///c:/tmp/test.txt". In other cases, the 45 | // colon is simply omitted, as in "file:///c/tmp/test.txt". 46 | path = path.replace(/^(.+)\|/, '$1:'); 47 | 48 | // for Windows, we need to invert the path separators from what a URI uses 49 | if (sep === '\\') { 50 | path = path.replace(/\//g, '\\'); 51 | } 52 | 53 | if (/^.+:/.test(path)) { 54 | // has Windows drive at beginning of path 55 | } else { 56 | // unix path… 57 | path = sep + path; 58 | } 59 | 60 | return host + path; 61 | } 62 | 63 | export = fileUriToPath; 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | file-uri-to-path 2 | ================ 3 | ### Convert a `file:` URI to a file path 4 | [![Build Status](https://github.com/TooTallNate/file-uri-to-path/workflows/Node%20CI/badge.svg)](https://github.com/TooTallNate/file-uri-to-path/actions?workflow=Node+CI) 5 | 6 | Accepts a `file:` URI and returns a regular file path suitable for use with the 7 | `fs` module functions. 8 | 9 | 10 | Installation 11 | ------------ 12 | 13 | Install with `npm`: 14 | 15 | ``` bash 16 | $ npm install file-uri-to-path 17 | ``` 18 | 19 | 20 | Example 21 | ------- 22 | 23 | ``` js 24 | var uri2path = require('file-uri-to-path'); 25 | 26 | uri2path('file://localhost/c|/WINDOWS/clock.avi'); 27 | // "c:\\WINDOWS\\clock.avi" 28 | 29 | uri2path('file:///c|/WINDOWS/clock.avi'); 30 | // "c:\\WINDOWS\\clock.avi" 31 | 32 | uri2path('file://localhost/c:/WINDOWS/clock.avi'); 33 | // "c:\\WINDOWS\\clock.avi" 34 | 35 | uri2path('file://hostname/path/to/the%20file.txt'); 36 | // "\\\\hostname\\path\\to\\the file.txt" 37 | 38 | uri2path('file:///c:/path/to/the%20file.txt'); 39 | // "c:\\path\\to\\the file.txt" 40 | ``` 41 | 42 | 43 | API 44 | --- 45 | 46 | ### fileUriToPath(String uri) → String 47 | 48 | 49 | 50 | License 51 | ------- 52 | 53 | (The MIT License) 54 | 55 | Copyright (c) 2014 Nathan Rajlich <nathan@tootallnate.net> 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining 58 | a copy of this software and associated documentation files (the 59 | 'Software'), to deal in the Software without restriction, including 60 | without limitation the rights to use, copy, modify, merge, publish, 61 | distribute, sublicense, and/or sell copies of the Software, and to 62 | permit persons to whom the Software is furnished to do so, subject to 63 | the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be 66 | included in all copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 69 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 70 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 71 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 72 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 73 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 74 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 75 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'airbnb', 4 | 'prettier' 5 | ], 6 | 'parser': '@typescript-eslint/parser', 7 | 'parserOptions': { 8 | 'ecmaVersion': 2018, 9 | 'sourceType': 'module', 10 | 'modules': true 11 | }, 12 | 'plugins': [ 13 | '@typescript-eslint' 14 | ], 15 | 'settings': { 16 | 'import/resolver': { 17 | 'typescript': { 18 | } 19 | } 20 | }, 21 | 'rules': { 22 | 'quotes': [ 23 | 2, 24 | 'single', 25 | { 26 | 'allowTemplateLiterals': true 27 | } 28 | ], 29 | 'class-methods-use-this': 0, 30 | 'consistent-return': 0, 31 | 'func-names': 0, 32 | 'global-require': 0, 33 | 'guard-for-in': 0, 34 | 'import/no-duplicates': 0, 35 | 'import/no-dynamic-require': 0, 36 | 'import/no-extraneous-dependencies': 0, 37 | 'import/prefer-default-export': 0, 38 | 'lines-between-class-members': 0, 39 | 'no-await-in-loop': 0, 40 | 'no-bitwise': 0, 41 | 'no-console': 0, 42 | 'no-continue': 0, 43 | 'no-control-regex': 0, 44 | 'no-empty': 0, 45 | 'no-loop-func': 0, 46 | 'no-nested-ternary': 0, 47 | 'no-param-reassign': 0, 48 | 'no-plusplus': 0, 49 | 'no-restricted-globals': 0, 50 | 'no-restricted-syntax': 0, 51 | 'no-shadow': 0, 52 | 'no-underscore-dangle': 0, 53 | 'no-use-before-define': 0, 54 | 'prefer-const': 0, 55 | 'prefer-destructuring': 0, 56 | 'camelcase': 0, 57 | 'no-unused-vars': 0, // in favor of '@typescript-eslint/no-unused-vars' 58 | // 'indent': 0 // in favor of '@typescript-eslint/indent' 59 | '@typescript-eslint/no-unused-vars': 'warn', 60 | // '@typescript-eslint/indent': ['error', 2] // this might conflict with a lot ongoing changes 61 | '@typescript-eslint/no-array-constructor': 'error', 62 | '@typescript-eslint/adjacent-overload-signatures': 'error', 63 | '@typescript-eslint/class-name-casing': 'error', 64 | '@typescript-eslint/interface-name-prefix': 'error', 65 | '@typescript-eslint/no-empty-interface': 'error', 66 | '@typescript-eslint/no-inferrable-types': 'error', 67 | '@typescript-eslint/no-misused-new': 'error', 68 | '@typescript-eslint/no-namespace': 'error', 69 | '@typescript-eslint/no-non-null-assertion': 'error', 70 | '@typescript-eslint/no-parameter-properties': 'error', 71 | '@typescript-eslint/no-triple-slash-reference': 'error', 72 | '@typescript-eslint/prefer-namespace-keyword': 'error', 73 | '@typescript-eslint/type-annotation-spacing': 'error', 74 | // '@typescript-eslint/array-type': 'error', 75 | // '@typescript-eslint/ban-types': 'error', 76 | // '@typescript-eslint/explicit-function-return-type': 'warn', 77 | // '@typescript-eslint/explicit-member-accessibility': 'error', 78 | // '@typescript-eslint/member-delimiter-style': 'error', 79 | // '@typescript-eslint/no-angle-bracket-type-assertion': 'error', 80 | // '@typescript-eslint/no-explicit-any': 'warn', 81 | // '@typescript-eslint/no-object-literal-type-assertion': 'error', 82 | // '@typescript-eslint/no-use-before-define': 'error', 83 | // '@typescript-eslint/no-var-requires': 'error', 84 | // '@typescript-eslint/prefer-interface': 'error' 85 | } 86 | } 87 | --------------------------------------------------------------------------------