diff options
Diffstat (limited to 'src/shared/settings/Blacklist.ts')
-rw-r--r-- | src/shared/settings/Blacklist.ts | 124 |
1 files changed, 105 insertions, 19 deletions
diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts index a95b606..0cfbd71 100644 --- a/src/shared/settings/Blacklist.ts +++ b/src/shared/settings/Blacklist.ts @@ -1,39 +1,125 @@ -export type BlacklistJSON = string[]; +import Key from './Key'; -const fromWildcard = (pattern: string): RegExp => { +export type BlacklistItemJSON = string | { + url: string, + keys: string[], +}; + +export type BlacklistJSON = BlacklistItemJSON[]; + +const regexFromWildcard = (pattern: string): RegExp => { let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$'; return new RegExp(regexStr); }; +const isArrayOfString = (raw: any): boolean => { + if (!Array.isArray(raw)) { + return false; + } + for (let x of Array.from(raw)) { + if (typeof x !== 'string') { + return false; + } + } + return true; +}; + +export class BlacklistItem { + public readonly pattern: string; + + private regex: RegExp; + + public readonly partial: boolean; + + public readonly keys: string[]; + + private readonly keyEntities: Key[]; + + constructor( + pattern: string, + partial: boolean, + keys: string[] + ) { + this.pattern = pattern; + this.regex = regexFromWildcard(pattern); + this.partial = partial; + this.keys = keys; + this.keyEntities = this.keys.map(Key.fromMapKey); + } + + static fromJSON(raw: any): BlacklistItem { + if (typeof raw === 'string') { + return new BlacklistItem(raw, false, []); + } else if (typeof raw === 'object' && raw !== null) { + if (!('url' in raw)) { + throw new TypeError( + `missing field "url" of blacklist item: ${JSON.stringify(raw)}`); + } + if (typeof raw.url !== 'string') { + throw new TypeError( + `invalid field "url" of blacklist item: ${JSON.stringify(raw)}`); + } + if (!('keys' in raw)) { + throw new TypeError( + `missing field "keys" of blacklist item: ${JSON.stringify(raw)}`); + } + if (!isArrayOfString(raw.keys)) { + throw new TypeError( + `invalid field "keys" of blacklist item: ${JSON.stringify(raw)}`); + } + return new BlacklistItem(raw.url as string, true, raw.keys as string[]); + } + throw new TypeError( + `invalid format of blacklist item: ${JSON.stringify(raw)}`); + } + + toJSON(): BlacklistItemJSON { + if (!this.partial) { + return this.pattern; + } + return { url: this.pattern, keys: this.keys }; + } + + matches(url: URL): boolean { + return this.pattern.includes('/') + ? this.regex.test(url.host + url.pathname) + : this.regex.test(url.host); + } + + includeKey(url: URL, key: Key): boolean { + if (!this.matches(url)) { + return false; + } + if (!this.partial) { + return true; + } + return this.keyEntities.some(k => k.equals(key)); + } +} + export default class Blacklist { constructor( - private blacklist: string[], + public readonly items: BlacklistItem[], ) { } static fromJSON(json: any): Blacklist { if (!Array.isArray(json)) { - throw new TypeError(`"blacklist" is not an array of string`); + throw new TypeError('blacklist is not an array: ' + JSON.stringify(json)); } - for (let x of json) { - if (typeof x !== 'string') { - throw new TypeError(`"blacklist" is not an array of string`); - } - } - return new Blacklist(json); + let items = Array.from(json).map(item => BlacklistItem.fromJSON(item)); + return new Blacklist(items); } toJSON(): BlacklistJSON { - return this.blacklist; + return this.items.map(item => item.toJSON()); } - includes(url: string): boolean { - let u = new URL(url); - return this.blacklist.some((item) => { - if (!item.includes('/')) { - return fromWildcard(item).test(u.host); - } - return fromWildcard(item).test(u.host + u.pathname); - }); + includesEntireBlacklist(url: URL): boolean { + return this.items.some(item => !item.partial && item.matches(url)); + } + + includeKey(url: URL, key: Key) { + return this.items.some(item => item.includeKey(url, key)); } } |