add hw2
This commit is contained in:
2
node_modules/@braintree/sanitize-url/.eslintignore
generated
vendored
Normal file
2
node_modules/@braintree/sanitize-url/.eslintignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
dist
|
||||
coverage
|
||||
6
node_modules/@braintree/sanitize-url/.eslintrc
generated
vendored
Normal file
6
node_modules/@braintree/sanitize-url/.eslintrc
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "braintree/client",
|
||||
"rules": {
|
||||
"no-control-regex": 0
|
||||
}
|
||||
}
|
||||
1
node_modules/@braintree/sanitize-url/.github/CODEOWNERS
generated
vendored
Normal file
1
node_modules/@braintree/sanitize-url/.github/CODEOWNERS
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @braintree/team-sdk-js
|
||||
17
node_modules/@braintree/sanitize-url/.github/workflows/ci.yml
generated
vendored
Normal file
17
node_modules/@braintree/sanitize-url/.github/workflows/ci.yml
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: "Unit Tests"
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Unit Tests on Ubuntu"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "18.x"
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
1
node_modules/@braintree/sanitize-url/.nvmrc
generated
vendored
Normal file
1
node_modules/@braintree/sanitize-url/.nvmrc
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
v18
|
||||
3
node_modules/@braintree/sanitize-url/.prettierignore
generated
vendored
Normal file
3
node_modules/@braintree/sanitize-url/.prettierignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*-lock.json
|
||||
dist
|
||||
coverage
|
||||
142
node_modules/@braintree/sanitize-url/CHANGELOG.md
generated
vendored
Normal file
142
node_modules/@braintree/sanitize-url/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 7.1.1
|
||||
|
||||
- DevDependency Changes
|
||||
|
||||
- happy-dom to 15.11.6
|
||||
|
||||
- Update (sub-)dependencies
|
||||
- cross-spawn to 7.0.6
|
||||
- micromatch to 4.0.8
|
||||
- vite to 4.5.5
|
||||
|
||||
## 7.1.0
|
||||
|
||||
- Updated to handle back-slashes
|
||||
|
||||
## 7.0.4
|
||||
|
||||
- Updates get-func-name to 2.0.2
|
||||
|
||||
## 7.0.3
|
||||
|
||||
- Dependencies
|
||||
- Update braces to 3.0.3
|
||||
|
||||
## 7.0.2
|
||||
|
||||
- Improve sanitization of whitespace escapes
|
||||
|
||||
## 7.0.1
|
||||
|
||||
- Improve sanitization of HTML entities
|
||||
|
||||
## 7.0.0
|
||||
|
||||
- Move constant declarations from index file to `constants.ts` file
|
||||
- Update to node v18
|
||||
|
||||
- Dev Dependency Updates
|
||||
- Update to TypeScript 5
|
||||
- Other minor dependency updates
|
||||
|
||||
## 6.0.4
|
||||
|
||||
- Add additional null byte sanitization prior to html decoding (#48)
|
||||
|
||||
## 6.0.3
|
||||
|
||||
- Add null check to beginning of `sanitizeUrl` function ([#54](https://github.com/braintree/sanitize-url/issues/54))
|
||||
|
||||
## 6.0.2
|
||||
|
||||
- Fix issue where urls in the form `https://example.com

/something` were not properly sanitized
|
||||
|
||||
## 6.0.1
|
||||
|
||||
- Fix issue where urls in the form `javascript:alert('xss');` were not properly sanitized
|
||||
- Fix issue where urls in the form `javasc	ript:alert('XSS');` were not properly sanitized
|
||||
|
||||
## 6.0.0
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
- Decode HTML characters automatically that would result in an XSS vulnerability when rendering links via a server rendered HTML file
|
||||
|
||||
```js
|
||||
// decodes to javacript:alert('XSS')
|
||||
const vulnerableUrl =
|
||||
"javascript:alert('XSS')";
|
||||
|
||||
sanitizeUrl(vulnerableUrl); // 'about:blank'
|
||||
|
||||
const okUrl = "https://example.com/" + vulnerableUrl;
|
||||
|
||||
// since the javascript bit is in the path instead of the protocol
|
||||
// this is successfully sanitized
|
||||
sanitizeUrl(okUrl); // 'https://example.com/javascript:alert('XSS');
|
||||
```
|
||||
|
||||
## 5.0.2
|
||||
|
||||
- Fix issue where certain invisible white space characters were not being sanitized (#35)
|
||||
|
||||
## 5.0.1
|
||||
|
||||
- Fix issue where certain safe characters were being filtered out (#31 thanks @akirchmyer)
|
||||
|
||||
## 5.0.0
|
||||
|
||||
_Breaking Changes_
|
||||
|
||||
- Sanitize vbscript urls (thanks @vicnicius)
|
||||
|
||||
## 4.1.1
|
||||
|
||||
- Fixup path to type declaration (closes #25)
|
||||
|
||||
## 4.1.0
|
||||
|
||||
- Add typescript types
|
||||
|
||||
## 4.0.1
|
||||
|
||||
- Fix issue where urls with accented characters were incorrectly sanitized
|
||||
|
||||
## 4.0.0
|
||||
|
||||
_Breaking Changes_
|
||||
|
||||
- Protocol-less urls (ie: www.example.com) will be sanitised and passed on instead of sending out `about:blank` (Thanks @chawes13 #18)
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- Trim whitespace from urls
|
||||
|
||||
## 3.0.0
|
||||
|
||||
_breaking changes_
|
||||
|
||||
- Replace blank strings with about:blank
|
||||
- Replace null values with about:blank
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- Allow relative urls to be sanitized
|
||||
|
||||
## 2.0.2
|
||||
|
||||
- Sanitize malicious URLs that begin with `\s`
|
||||
|
||||
## 2.0.1
|
||||
|
||||
- Sanitize malicious URLs that begin with %20
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- sanitize data: urls
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- sanitize javascript: urls
|
||||
21
node_modules/@braintree/sanitize-url/LICENSE
generated
vendored
Normal file
21
node_modules/@braintree/sanitize-url/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Braintree
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
39
node_modules/@braintree/sanitize-url/README.md
generated
vendored
Normal file
39
node_modules/@braintree/sanitize-url/README.md
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# sanitize-url
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install -S @braintree/sanitize-url
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var sanitizeUrl = require("@braintree/sanitize-url").sanitizeUrl;
|
||||
|
||||
sanitizeUrl("https://example.com"); // 'https://example.com'
|
||||
sanitizeUrl("http://example.com"); // 'http://example.com'
|
||||
sanitizeUrl("www.example.com"); // 'www.example.com'
|
||||
sanitizeUrl("mailto:hello@example.com"); // 'mailto:hello@example.com'
|
||||
sanitizeUrl(
|
||||
"https://example.com"
|
||||
); // https://example.com
|
||||
|
||||
sanitizeUrl("javascript:alert(document.domain)"); // 'about:blank'
|
||||
sanitizeUrl("jAvasCrIPT:alert(document.domain)"); // 'about:blank'
|
||||
sanitizeUrl(decodeURIComponent("JaVaScRiP%0at:alert(document.domain)")); // 'about:blank'
|
||||
// HTML encoded javascript:alert('XSS')
|
||||
sanitizeUrl(
|
||||
"javascript:alert('XSS')"
|
||||
); // 'about:blank'
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
This library uses [Vitest](https://vitest.dev/). All testing dependencies
|
||||
will be installed upon `npm install` and the test suite can be executed with
|
||||
`npm test`. Running the test suite will also run lint checks upon exiting.
|
||||
|
||||
npm test
|
||||
|
||||
To generate a coverage report, use `npm run coverage`.
|
||||
8
node_modules/@braintree/sanitize-url/dist/constants.d.ts
generated
vendored
Normal file
8
node_modules/@braintree/sanitize-url/dist/constants.d.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
export declare const invalidProtocolRegex: RegExp;
|
||||
export declare const htmlEntitiesRegex: RegExp;
|
||||
export declare const htmlCtrlEntityRegex: RegExp;
|
||||
export declare const ctrlCharactersRegex: RegExp;
|
||||
export declare const urlSchemeRegex: RegExp;
|
||||
export declare const whitespaceEscapeCharsRegex: RegExp;
|
||||
export declare const relativeFirstCharacters: string[];
|
||||
export declare const BLANK_URL = "about:blank";
|
||||
11
node_modules/@braintree/sanitize-url/dist/constants.js
generated
vendored
Normal file
11
node_modules/@braintree/sanitize-url/dist/constants.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BLANK_URL = exports.relativeFirstCharacters = exports.whitespaceEscapeCharsRegex = exports.urlSchemeRegex = exports.ctrlCharactersRegex = exports.htmlCtrlEntityRegex = exports.htmlEntitiesRegex = exports.invalidProtocolRegex = void 0;
|
||||
exports.invalidProtocolRegex = /^([^\w]*)(javascript|data|vbscript)/im;
|
||||
exports.htmlEntitiesRegex = /&#(\w+)(^\w|;)?/g;
|
||||
exports.htmlCtrlEntityRegex = /&(newline|tab);/gi;
|
||||
exports.ctrlCharactersRegex = /[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;
|
||||
exports.urlSchemeRegex = /^.+(:|:)/gim;
|
||||
exports.whitespaceEscapeCharsRegex = /(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g;
|
||||
exports.relativeFirstCharacters = [".", "/"];
|
||||
exports.BLANK_URL = "about:blank";
|
||||
1
node_modules/@braintree/sanitize-url/dist/index.d.ts
generated
vendored
Normal file
1
node_modules/@braintree/sanitize-url/dist/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function sanitizeUrl(url?: string): string;
|
||||
81
node_modules/@braintree/sanitize-url/dist/index.js
generated
vendored
Normal file
81
node_modules/@braintree/sanitize-url/dist/index.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sanitizeUrl = void 0;
|
||||
var constants_1 = require("./constants");
|
||||
function isRelativeUrlWithoutProtocol(url) {
|
||||
return constants_1.relativeFirstCharacters.indexOf(url[0]) > -1;
|
||||
}
|
||||
function decodeHtmlCharacters(str) {
|
||||
var removedNullByte = str.replace(constants_1.ctrlCharactersRegex, "");
|
||||
return removedNullByte.replace(constants_1.htmlEntitiesRegex, function (match, dec) {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
}
|
||||
function isValidUrl(url) {
|
||||
return URL.canParse(url);
|
||||
}
|
||||
function decodeURI(uri) {
|
||||
try {
|
||||
return decodeURIComponent(uri);
|
||||
}
|
||||
catch (e) {
|
||||
// Ignoring error
|
||||
// It is possible that the URI contains a `%` not associated
|
||||
// with URI/URL-encoding.
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
function sanitizeUrl(url) {
|
||||
if (!url) {
|
||||
return constants_1.BLANK_URL;
|
||||
}
|
||||
var charsToDecode;
|
||||
var decodedUrl = decodeURI(url.trim());
|
||||
do {
|
||||
decodedUrl = decodeHtmlCharacters(decodedUrl)
|
||||
.replace(constants_1.htmlCtrlEntityRegex, "")
|
||||
.replace(constants_1.ctrlCharactersRegex, "")
|
||||
.replace(constants_1.whitespaceEscapeCharsRegex, "")
|
||||
.trim();
|
||||
decodedUrl = decodeURI(decodedUrl);
|
||||
charsToDecode =
|
||||
decodedUrl.match(constants_1.ctrlCharactersRegex) ||
|
||||
decodedUrl.match(constants_1.htmlEntitiesRegex) ||
|
||||
decodedUrl.match(constants_1.htmlCtrlEntityRegex) ||
|
||||
decodedUrl.match(constants_1.whitespaceEscapeCharsRegex);
|
||||
} while (charsToDecode && charsToDecode.length > 0);
|
||||
var sanitizedUrl = decodedUrl;
|
||||
if (!sanitizedUrl) {
|
||||
return constants_1.BLANK_URL;
|
||||
}
|
||||
if (isRelativeUrlWithoutProtocol(sanitizedUrl)) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
// Remove any leading whitespace before checking the URL scheme
|
||||
var trimmedUrl = sanitizedUrl.trimStart();
|
||||
var urlSchemeParseResults = trimmedUrl.match(constants_1.urlSchemeRegex);
|
||||
if (!urlSchemeParseResults) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
var urlScheme = urlSchemeParseResults[0].toLowerCase().trim();
|
||||
if (constants_1.invalidProtocolRegex.test(urlScheme)) {
|
||||
return constants_1.BLANK_URL;
|
||||
}
|
||||
var backSanitized = trimmedUrl.replace(/\\/g, "/");
|
||||
// Handle special cases for mailto: and custom deep-link protocols
|
||||
if (urlScheme === "mailto:" || urlScheme.includes("://")) {
|
||||
return backSanitized;
|
||||
}
|
||||
// For http and https URLs, perform additional validation
|
||||
if (urlScheme === "http:" || urlScheme === "https:") {
|
||||
if (!isValidUrl(backSanitized)) {
|
||||
return constants_1.BLANK_URL;
|
||||
}
|
||||
var url_1 = new URL(backSanitized);
|
||||
url_1.protocol = url_1.protocol.toLowerCase();
|
||||
url_1.hostname = url_1.hostname.toLowerCase();
|
||||
return url_1.toString();
|
||||
}
|
||||
return backSanitized;
|
||||
}
|
||||
exports.sanitizeUrl = sanitizeUrl;
|
||||
40
node_modules/@braintree/sanitize-url/package.json
generated
vendored
Normal file
40
node_modules/@braintree/sanitize-url/package.json
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@braintree/sanitize-url",
|
||||
"version": "7.1.1",
|
||||
"description": "A url sanitizer",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build",
|
||||
"prebuild": "prettier --write .",
|
||||
"build": "tsc --declaration",
|
||||
"lint": "eslint --ext js,ts .",
|
||||
"posttest": "npm run lint",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/braintree/sanitize-url.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/braintree/sanitize-url/issues"
|
||||
},
|
||||
"homepage": "https://github.com/braintree/sanitize-url#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@vitest/coverage-v8": "^0.34.2",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-braintree": "^6.0.0-typescript-prep-rc.2",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"happy-dom": "^15.11.6",
|
||||
"prettier": "^2.8.4",
|
||||
"typescript": "^5.1.6",
|
||||
"vitest": "^0.34.2"
|
||||
}
|
||||
}
|
||||
261
node_modules/@braintree/sanitize-url/src/__tests__/index.test.ts
generated
vendored
Normal file
261
node_modules/@braintree/sanitize-url/src/__tests__/index.test.ts
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
/* eslint-disable no-script-url */
|
||||
import { sanitizeUrl } from "..";
|
||||
import { BLANK_URL } from "../constants";
|
||||
|
||||
describe("sanitizeUrl", () => {
|
||||
it("does not alter http URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("http://example.com/path/to:something")).toBe(
|
||||
"http://example.com/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter http URLs with ports with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("http://example.com:4567/path/to:something")).toBe(
|
||||
"http://example.com:4567/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter https URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("https://example.com")).toBe("https://example.com/");
|
||||
});
|
||||
|
||||
it("does not alter https URLs with ports with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("https://example.com:4567/path/to:something")).toBe(
|
||||
"https://example.com:4567/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter relative-path reference URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("./path/to/my.json")).toBe("./path/to/my.json");
|
||||
});
|
||||
|
||||
it("does not alter absolute-path reference URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("/path/to/my.json")).toBe("/path/to/my.json");
|
||||
});
|
||||
|
||||
it("does not alter protocol-less network-path URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("//google.com/robots.txt")).toBe(
|
||||
"//google.com/robots.txt"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter protocol-less URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("www.example.com")).toBe("www.example.com");
|
||||
});
|
||||
|
||||
it("does not alter deep-link urls with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("com.braintreepayments.demo://example")).toBe(
|
||||
"com.braintreepayments.demo://example"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter mailto urls with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("mailto:test@example.com?subject=hello+world")).toBe(
|
||||
"mailto:test@example.com?subject=hello+world"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter urls with accented characters", () => {
|
||||
expect(sanitizeUrl("www.example.com/with-áccêntš")).toBe(
|
||||
"www.example.com/with-áccêntš"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not strip harmless unicode characters", () => {
|
||||
expect(sanitizeUrl("www.example.com/лот.рфшишкиü–")).toBe(
|
||||
"www.example.com/лот.рфшишкиü–"
|
||||
);
|
||||
});
|
||||
|
||||
it("strips out ctrl chars", () => {
|
||||
expect(
|
||||
sanitizeUrl("www.example.com/\u200D\u0000\u001F\x00\x1F\uFEFFfoo")
|
||||
).toBe("www.example.com/foo");
|
||||
});
|
||||
|
||||
it(`replaces blank urls with ${BLANK_URL}`, () => {
|
||||
expect(sanitizeUrl("")).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
it(`replaces null values with ${BLANK_URL}`, () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(sanitizeUrl(null)).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
it(`replaces undefined values with ${BLANK_URL}`, () => {
|
||||
expect(sanitizeUrl()).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
it("removes whitespace from urls", () => {
|
||||
expect(sanitizeUrl(" http://example.com/path/to:something ")).toBe(
|
||||
"http://example.com/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("removes newline entities from urls", () => {
|
||||
expect(sanitizeUrl("https://example.com

/something")).toBe(
|
||||
"https://example.com/something"
|
||||
);
|
||||
});
|
||||
|
||||
it("decodes html entities", () => {
|
||||
// all these decode to javascript:alert('xss');
|
||||
const attackVectors = [
|
||||
"javascript:alert('XSS')",
|
||||
"javascript:alert('XSS')",
|
||||
"javascript:alert('XSS')",
|
||||
"jav	ascript:alert('XSS');",
|
||||
"  javascript:alert('XSS');",
|
||||
"javasc	ript: alert('XSS');",
|
||||
"javasc&#\u0000x09;ript:alert(1)",
|
||||
"java&NewLine&newline;;script:alert('XSS')",
|
||||
"java&NewLine&newline;;script:alert('XSS')",
|
||||
];
|
||||
|
||||
attackVectors.forEach((vector) => {
|
||||
expect(sanitizeUrl(vector)).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
// https://example.com/javascript:alert('XSS')
|
||||
// since the javascript is the url path, and not the protocol,
|
||||
// this url is technically sanitized
|
||||
expect(
|
||||
sanitizeUrl(
|
||||
"https://example.com/javascript:alert('XSS')"
|
||||
)
|
||||
).toBe("https://example.com/javascript:alert('XSS')");
|
||||
});
|
||||
|
||||
it("removes whitespace escape sequences", () => {
|
||||
const attackVectors = [
|
||||
"javascri\npt:alert('xss')",
|
||||
"javascri\rpt:alert('xss')",
|
||||
"javascri\tpt:alert('xss')",
|
||||
"javascrip\\%74t:alert('XSS')",
|
||||
"javascrip%5c%72t:alert()",
|
||||
"javascrip%5Ctt:alert()",
|
||||
"javascrip%255Ctt:alert()",
|
||||
"javascrip%25%35Ctt:alert()",
|
||||
"javascrip%25%35%43tt:alert()",
|
||||
"javascrip%25%32%35%25%33%35%25%34%33rt:alert()",
|
||||
"javascrip%255Crt:alert('%25xss')",
|
||||
];
|
||||
|
||||
attackVectors.forEach((vector) => {
|
||||
expect(sanitizeUrl(vector)).toBe(BLANK_URL);
|
||||
});
|
||||
});
|
||||
|
||||
it("backslash prefixed attack vectors", () => {
|
||||
const attackVectors = [
|
||||
"\fjavascript:alert()",
|
||||
"\vjavascript:alert()",
|
||||
"\tjavascript:alert()",
|
||||
"\njavascript:alert()",
|
||||
"\rjavascript:alert()",
|
||||
"\u0000javascript:alert()",
|
||||
"\u0001javascript:alert()",
|
||||
];
|
||||
|
||||
attackVectors.forEach((vector) => {
|
||||
expect(sanitizeUrl(vector)).toBe(BLANK_URL);
|
||||
});
|
||||
});
|
||||
|
||||
it("reverses backslashes", () => {
|
||||
const attack = "\\j\\av\\a\\s\\cript:alert()";
|
||||
|
||||
expect(sanitizeUrl(attack)).toBe("/j/av/a/s/cript:alert()");
|
||||
});
|
||||
|
||||
describe("invalid protocols", () => {
|
||||
describe.each(["javascript", "data", "vbscript"])("%s", (protocol) => {
|
||||
it(`replaces ${protocol} urls with ${BLANK_URL}`, () => {
|
||||
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
||||
BLANK_URL
|
||||
);
|
||||
});
|
||||
|
||||
it(`allows ${protocol} urls that start with a letter prefix`, () => {
|
||||
expect(sanitizeUrl(`not_${protocol}:alert(document.domain)`)).toBe(
|
||||
`not_${protocol}:alert(document.domain)`
|
||||
);
|
||||
});
|
||||
|
||||
it(`disallows ${protocol} urls that start with non-\w characters as a suffix for the protocol`, () => {
|
||||
expect(sanitizeUrl(`&!*${protocol}:alert(document.domain)`)).toBe(
|
||||
BLANK_URL
|
||||
);
|
||||
});
|
||||
|
||||
it(`disallows ${protocol} urls that use : for the colon portion of the url`, () => {
|
||||
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
||||
BLANK_URL
|
||||
);
|
||||
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
||||
BLANK_URL
|
||||
);
|
||||
});
|
||||
|
||||
it(`disregards capitalization for ${protocol} urls`, () => {
|
||||
// upper case every other letter in protocol name
|
||||
const mixedCapitalizationProtocol = protocol
|
||||
.split("")
|
||||
.map((character, index) => {
|
||||
if (index % 2 === 0) {
|
||||
return character.toUpperCase();
|
||||
}
|
||||
return character;
|
||||
})
|
||||
.join("");
|
||||
|
||||
expect(
|
||||
sanitizeUrl(`${mixedCapitalizationProtocol}:alert(document.domain)`)
|
||||
).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
it(`ignores invisible ctrl characters in ${protocol} urls`, () => {
|
||||
const protocolWithControlCharacters = protocol
|
||||
.split("")
|
||||
.map((character, index) => {
|
||||
if (index === 1) {
|
||||
return character + "%EF%BB%BF%EF%BB%BF";
|
||||
} else if (index === 2) {
|
||||
return character + "%e2%80%8b";
|
||||
}
|
||||
return character;
|
||||
})
|
||||
.join("");
|
||||
|
||||
expect(
|
||||
sanitizeUrl(
|
||||
decodeURIComponent(
|
||||
`${protocolWithControlCharacters}:alert(document.domain)`
|
||||
)
|
||||
)
|
||||
).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
it(`replaces ${protocol} urls with ${BLANK_URL} when url begins with %20`, () => {
|
||||
expect(
|
||||
sanitizeUrl(
|
||||
decodeURIComponent(`%20%20%20%20${protocol}:alert(document.domain)`)
|
||||
)
|
||||
).toBe(BLANK_URL);
|
||||
});
|
||||
|
||||
it(`replaces ${protocol} urls with ${BLANK_URL} when ${protocol} url begins with spaces`, () => {
|
||||
expect(sanitizeUrl(` ${protocol}:alert(document.domain)`)).toBe(
|
||||
BLANK_URL
|
||||
);
|
||||
});
|
||||
|
||||
it(`does not replace ${protocol}: if it is not in the scheme of the URL`, () => {
|
||||
expect(sanitizeUrl(`http://example.com#${protocol}:foo`)).toBe(
|
||||
`http://example.com#${protocol}:foo`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
10
node_modules/@braintree/sanitize-url/src/constants.ts
generated
vendored
Normal file
10
node_modules/@braintree/sanitize-url/src/constants.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export const invalidProtocolRegex = /^([^\w]*)(javascript|data|vbscript)/im;
|
||||
export const htmlEntitiesRegex = /&#(\w+)(^\w|;)?/g;
|
||||
export const htmlCtrlEntityRegex = /&(newline|tab);/gi;
|
||||
export const ctrlCharactersRegex =
|
||||
/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;
|
||||
export const urlSchemeRegex = /^.+(:|:)/gim;
|
||||
export const whitespaceEscapeCharsRegex =
|
||||
/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g;
|
||||
export const relativeFirstCharacters = [".", "/"];
|
||||
export const BLANK_URL = "about:blank";
|
||||
107
node_modules/@braintree/sanitize-url/src/index.ts
generated
vendored
Normal file
107
node_modules/@braintree/sanitize-url/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
BLANK_URL,
|
||||
ctrlCharactersRegex,
|
||||
htmlCtrlEntityRegex,
|
||||
htmlEntitiesRegex,
|
||||
invalidProtocolRegex,
|
||||
relativeFirstCharacters,
|
||||
whitespaceEscapeCharsRegex,
|
||||
urlSchemeRegex,
|
||||
} from "./constants";
|
||||
|
||||
function isRelativeUrlWithoutProtocol(url: string): boolean {
|
||||
return relativeFirstCharacters.indexOf(url[0]) > -1;
|
||||
}
|
||||
|
||||
function decodeHtmlCharacters(str: string) {
|
||||
const removedNullByte = str.replace(ctrlCharactersRegex, "");
|
||||
return removedNullByte.replace(htmlEntitiesRegex, (match, dec) => {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
}
|
||||
|
||||
function isValidUrl(url: string): boolean {
|
||||
return URL.canParse(url);
|
||||
}
|
||||
|
||||
function decodeURI(uri: string): string {
|
||||
try {
|
||||
return decodeURIComponent(uri);
|
||||
} catch (e: unknown) {
|
||||
// Ignoring error
|
||||
// It is possible that the URI contains a `%` not associated
|
||||
// with URI/URL-encoding.
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizeUrl(url?: string): string {
|
||||
if (!url) {
|
||||
return BLANK_URL;
|
||||
}
|
||||
|
||||
let charsToDecode;
|
||||
let decodedUrl = decodeURI(url.trim());
|
||||
|
||||
do {
|
||||
decodedUrl = decodeHtmlCharacters(decodedUrl)
|
||||
.replace(htmlCtrlEntityRegex, "")
|
||||
.replace(ctrlCharactersRegex, "")
|
||||
.replace(whitespaceEscapeCharsRegex, "")
|
||||
.trim();
|
||||
|
||||
decodedUrl = decodeURI(decodedUrl);
|
||||
|
||||
charsToDecode =
|
||||
decodedUrl.match(ctrlCharactersRegex) ||
|
||||
decodedUrl.match(htmlEntitiesRegex) ||
|
||||
decodedUrl.match(htmlCtrlEntityRegex) ||
|
||||
decodedUrl.match(whitespaceEscapeCharsRegex);
|
||||
} while (charsToDecode && charsToDecode.length > 0);
|
||||
|
||||
const sanitizedUrl = decodedUrl;
|
||||
|
||||
if (!sanitizedUrl) {
|
||||
return BLANK_URL;
|
||||
}
|
||||
|
||||
if (isRelativeUrlWithoutProtocol(sanitizedUrl)) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
|
||||
// Remove any leading whitespace before checking the URL scheme
|
||||
const trimmedUrl = sanitizedUrl.trimStart();
|
||||
const urlSchemeParseResults = trimmedUrl.match(urlSchemeRegex);
|
||||
|
||||
if (!urlSchemeParseResults) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
|
||||
const urlScheme = urlSchemeParseResults[0].toLowerCase().trim();
|
||||
|
||||
if (invalidProtocolRegex.test(urlScheme)) {
|
||||
return BLANK_URL;
|
||||
}
|
||||
|
||||
const backSanitized = trimmedUrl.replace(/\\/g, "/");
|
||||
|
||||
// Handle special cases for mailto: and custom deep-link protocols
|
||||
if (urlScheme === "mailto:" || urlScheme.includes("://")) {
|
||||
return backSanitized;
|
||||
}
|
||||
|
||||
// For http and https URLs, perform additional validation
|
||||
if (urlScheme === "http:" || urlScheme === "https:") {
|
||||
if (!isValidUrl(backSanitized)) {
|
||||
return BLANK_URL;
|
||||
}
|
||||
|
||||
const url = new URL(backSanitized);
|
||||
url.protocol = url.protocol.toLowerCase();
|
||||
url.hostname = url.hostname.toLowerCase();
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return backSanitized;
|
||||
}
|
||||
12
node_modules/@braintree/sanitize-url/tsconfig.json
generated
vendored
Normal file
12
node_modules/@braintree/sanitize-url/tsconfig.json
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"target": "es5",
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["es2015", "dom"]
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["**/__tests__/*"]
|
||||
}
|
||||
8
node_modules/@braintree/sanitize-url/vitest.config.ts
generated
vendored
Normal file
8
node_modules/@braintree/sanitize-url/vitest.config.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "happy-dom",
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user