完成世界书、骰子、apiconfig页面处理

This commit is contained in:
2026-04-30 01:35:10 +08:00
parent a3e3711b2b
commit ba9b925c32
4602 changed files with 785225 additions and 23 deletions

View File

@@ -0,0 +1,47 @@
name: Bug Report
description: File a bug report.
title: "[Bug]: "
labels: ["bug", "triage"]
projects: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Detail the bad behavior and the steps required to duplicate it
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: What did you expect to happen?
description: What did you expect to happen when performing the action?
placeholder: Expected behavior
value: "Expected behavior"
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of this library are you running?
placeholder: "v0.0.0"
value:
validations:
required: true
- type: textarea
id: browsers
attributes:
label: What browsers are you seeing the problem on?
description: |
List browsers where you see the problem reported. For example:
* Mac OS 15.3.5, Chrome 130.0.1234.987
* Android 14, Firefox 133.0
* iPadOS 17.1, Safari
placeholder: "For each browser specify 1) operating system and version 2) browser name and version"

View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Contact Developer Support
url: https://developer.paypal.com/braintree/help
about: If you need help troubleshooting your integration, reach out to Braintree Support. Only open a GitHub issue if you've found an issue with our SDK.

View File

@@ -0,0 +1,17 @@
# Summary of changes
-
## Checklist
- [ ] Added a changelog entry
- [ ] Relevant test coverage
- [ ] Tested and confirmed flows affected by this change are functioning as expected
## Authors
> List GitHub usernames for everyone who contributed to this pull request.
### Reviewers
@braintree/team-sdk-js

View 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&#0000058//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(
"&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041",
); // '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`.

View 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";

View 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";

View File

@@ -0,0 +1,263 @@
/* 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 = [
"&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041",
"javascript:alert('XSS')",
"&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29",
"jav	ascript:alert('XSS');",
"  javascript:alert('XSS');",
"javasc	ript: alert('XSS');",
"javasc&#\u0000x09;ript:alert(1)",
"java&&#78&#59;ewLine&newline&#59;&#59;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&#0000058//example.com/&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041",
),
).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`,
);
});
});
});
});