Make Blacklist class

jh-changes
Shin'ya UEOKA 5 years ago
parent 574692551a
commit b86b4680b6
  1. 6
      src/content/controllers/SettingController.ts
  2. 4
      src/settings/components/index.tsx
  3. 13
      src/shared/SettingData.ts
  4. 21
      src/shared/Settings.ts
  5. 13
      src/shared/blacklists.ts
  6. 39
      src/shared/settings/Blacklist.ts
  7. 6
      src/shared/utils/re.ts
  8. 11
      test/shared/SettingData.test.ts
  9. 30
      test/shared/Settings.test.ts
  10. 49
      test/shared/blacklists.test.ts
  11. 77
      test/shared/settings/Blacklist.test.ts
  12. 19
      test/shared/utils/re.test.ts

@ -1,8 +1,6 @@
import { injectable } from 'tsyringe'; import { injectable } from 'tsyringe';
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
import SettingUseCase from '../usecases/SettingUseCase'; import SettingUseCase from '../usecases/SettingUseCase';
import * as blacklists from '../../shared/blacklists';
import * as messages from '../../shared/messages'; import * as messages from '../../shared/messages';
@injectable() @injectable()
@ -17,9 +15,7 @@ export default class SettingController {
async initSettings(): Promise<void> { async initSettings(): Promise<void> {
try { try {
let current = await this.settingUseCase.reload(); let current = await this.settingUseCase.reload();
let disabled = blacklists.includes( let disabled = current.blacklist.includes(window.location.href);
current.blacklist, window.location.href,
);
if (disabled) { if (disabled) {
this.addonEnabledUseCase.disable(); this.addonEnabledUseCase.disable();
} else { } else {

@ -11,8 +11,8 @@ import SettingData, {
FormKeymaps, FormSearch, FormSettings, JSONTextSettings, FormKeymaps, FormSearch, FormSettings, JSONTextSettings,
} from '../../shared/SettingData'; } from '../../shared/SettingData';
import { State as AppState } from '../reducers/setting'; import { State as AppState } from '../reducers/setting';
import * as settings from '../../shared/Settings';
import Properties from '../../shared/settings/Properties'; import Properties from '../../shared/settings/Properties';
import Blacklist from '../../shared/settings/Blacklist';
const DO_YOU_WANT_TO_CONTINUE = const DO_YOU_WANT_TO_CONTINUE =
'Some settings in JSON can be lost when migrating. ' + 'Some settings in JSON can be lost when migrating. ' +
@ -143,7 +143,7 @@ class SettingsComponent extends React.Component<Props> {
let data = new SettingData({ let data = new SettingData({
source: this.props.source, source: this.props.source,
form: (this.props.form as FormSettings).buildWithBlacklist( form: (this.props.form as FormSettings).buildWithBlacklist(
settings.blacklistValueOf(value)), Blacklist.fromJSON(value)),
}); });
this.props.dispatch(settingActions.set(data)); this.props.dispatch(settingActions.set(data));
} }

@ -3,6 +3,7 @@ import Settings, * as settings from './Settings';
import Keymaps from './settings/Keymaps'; import Keymaps from './settings/Keymaps';
import Search from './settings/Search'; import Search from './settings/Search';
import Properties from './settings/Properties'; import Properties from './settings/Properties';
import Blacklist from './settings/Blacklist';
export class FormKeymaps { export class FormKeymaps {
private data: {[op: string]: string}; private data: {[op: string]: string};
@ -146,13 +147,13 @@ export class FormSettings {
private properties: Properties; private properties: Properties;
private blacklist: string[]; private blacklist: Blacklist;
constructor( constructor(
keymaps: FormKeymaps, keymaps: FormKeymaps,
search: FormSearch, search: FormSearch,
properties: Properties, properties: Properties,
blacklist: string[], blacklist: Blacklist,
) { ) {
this.keymaps = keymaps; this.keymaps = keymaps;
this.search = search; this.search = search;
@ -187,7 +188,7 @@ export class FormSettings {
); );
} }
buildWithBlacklist(blacklist: string[]): FormSettings { buildWithBlacklist(blacklist: Blacklist): FormSettings {
return new FormSettings( return new FormSettings(
this.keymaps, this.keymaps,
this.search, this.search,
@ -201,7 +202,7 @@ export class FormSettings {
keymaps: this.keymaps.toKeymaps().toJSON(), keymaps: this.keymaps.toKeymaps().toJSON(),
search: this.search.toSearchSettings().toJSON(), search: this.search.toSearchSettings().toJSON(),
properties: this.properties.toJSON(), properties: this.properties.toJSON(),
blacklist: this.blacklist, blacklist: this.blacklist.toJSON(),
}); });
} }
@ -215,7 +216,7 @@ export class FormSettings {
keymaps: this.keymaps.toJSON(), keymaps: this.keymaps.toJSON(),
search: this.search.toJSON(), search: this.search.toJSON(),
properties: this.properties.toJSON(), properties: this.properties.toJSON(),
blacklist: this.blacklist, blacklist: this.blacklist.toJSON(),
}; };
} }
@ -229,7 +230,7 @@ export class FormSettings {
FormKeymaps.valueOf(o.keymaps), FormKeymaps.valueOf(o.keymaps),
FormSearch.valueOf(o.search), FormSearch.valueOf(o.search),
Properties.fromJSON(o.properties), Properties.fromJSON(o.properties),
settings.blacklistValueOf(o.blacklist), Blacklist.fromJSON(o.blacklist),
); );
} }

@ -1,26 +1,15 @@
import Keymaps from './settings/Keymaps'; import Keymaps from './settings/Keymaps';
import Search from './settings/Search'; import Search from './settings/Search';
import Properties from './settings/Properties'; import Properties from './settings/Properties';
import Blacklist from './settings/Blacklist';
export default interface Settings { export default interface Settings {
keymaps: Keymaps; keymaps: Keymaps;
search: Search; search: Search;
properties: Properties; properties: Properties;
blacklist: string[]; blacklist: Blacklist;
} }
export const blacklistValueOf = (o: any): string[] => {
if (!Array.isArray(o)) {
throw new TypeError(`"blacklist" is not an array of string`);
}
for (let x of o) {
if (typeof x !== 'string') {
throw new TypeError(`"blacklist" is not an array of string`);
}
}
return o as string[];
};
export const valueOf = (o: any): Settings => { export const valueOf = (o: any): Settings => {
let settings = { ...DefaultSetting }; let settings = { ...DefaultSetting };
for (let key of Object.keys(o)) { for (let key of Object.keys(o)) {
@ -35,7 +24,7 @@ export const valueOf = (o: any): Settings => {
settings.properties = Properties.fromJSON(o.properties); settings.properties = Properties.fromJSON(o.properties);
break; break;
case 'blacklist': case 'blacklist':
settings.blacklist = blacklistValueOf(o.blacklist); settings.blacklist = Blacklist.fromJSON(o.blacklist);
break; break;
default: default:
throw new TypeError('unknown setting: ' + key); throw new TypeError('unknown setting: ' + key);
@ -49,7 +38,7 @@ export const toJSON = (settings: Settings): any => {
keymaps: settings.keymaps.toJSON(), keymaps: settings.keymaps.toJSON(),
search: settings.search.toJSON(), search: settings.search.toJSON(),
properties: settings.properties.toJSON(), properties: settings.properties.toJSON(),
blacklist: settings.blacklist, blacklist: settings.blacklist.toJSON(),
}; };
}; };
@ -134,5 +123,5 @@ export const DefaultSetting: Settings = {
smoothscroll: false, smoothscroll: false,
complete: 'sbh' complete: 'sbh'
}), }),
blacklist: [] blacklist: Blacklist.fromJSON([]),
}; };

@ -1,13 +0,0 @@
import * as re from './utils/re';
const includes = (blacklist: string[], url: string): boolean => {
let u = new URL(url);
return blacklist.some((item) => {
if (!item.includes('/')) {
return re.fromWildcard(item).test(u.host);
}
return re.fromWildcard(item).test(u.host + u.pathname);
});
};
export { includes };

@ -0,0 +1,39 @@
export type BlacklistJSON = string[];
const fromWildcard = (pattern: string): RegExp => {
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
return new RegExp(regexStr);
};
export default class Blacklist {
constructor(
private blacklist: string[],
) {
}
static fromJSON(json: any): Blacklist {
if (!Array.isArray(json)) {
throw new TypeError(`"blacklist" is not an array of string`);
}
for (let x of json) {
if (typeof x !== 'string') {
throw new TypeError(`"blacklist" is not an array of string`);
}
}
return new Blacklist(json);
}
toJSON(): BlacklistJSON {
return this.blacklist;
}
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);
});
}
}

@ -1,6 +0,0 @@
const fromWildcard = (pattern: string): RegExp => {
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
return new RegExp(regexStr);
};
export { fromWildcard };

@ -6,6 +6,7 @@ import { expect } from 'chai';
import Keymaps from '../../src/shared/settings/Keymaps'; import Keymaps from '../../src/shared/settings/Keymaps';
import Search from '../../src/shared/settings/Search'; import Search from '../../src/shared/settings/Search';
import Properties from '../../src/shared/settings/Properties'; import Properties from '../../src/shared/settings/Properties';
import Blacklist from '../../src/shared/settings/Blacklist'
describe('shared/SettingData', () => { describe('shared/SettingData', () => {
describe('FormKeymaps', () => { describe('FormKeymaps', () => {
@ -64,7 +65,7 @@ describe('shared/SettingData', () => {
keymaps: settings.keymaps.toJSON(), keymaps: settings.keymaps.toJSON(),
search: settings.search.toJSON(), search: settings.search.toJSON(),
properties: settings.properties.toJSON(), properties: settings.properties.toJSON(),
blacklist: settings.blacklist, blacklist: settings.blacklist.toJSON(),
}).to.deep.equal(JSON.parse(o)); }).to.deep.equal(JSON.parse(o));
}); });
}); });
@ -84,7 +85,7 @@ describe('shared/SettingData', () => {
smoothscroll: false, smoothscroll: false,
complete: "sbh" complete: "sbh"
}), }),
blacklist: [], blacklist: Blacklist.fromJSON([]),
}; };
let json = JSONTextSettings.fromSettings(o).toJSONText(); let json = JSONTextSettings.fromSettings(o).toJSONText();
@ -92,7 +93,7 @@ describe('shared/SettingData', () => {
keymaps: o.keymaps.toJSON(), keymaps: o.keymaps.toJSON(),
search: o.search.toJSON(), search: o.search.toJSON(),
properties: o.properties.toJSON(), properties: o.properties.toJSON(),
blacklist: o.blacklist, blacklist: o.blacklist.toJSON(),
}); });
}); });
}); });
@ -125,7 +126,7 @@ describe('shared/SettingData', () => {
keymaps: settings.keymaps.toJSON(), keymaps: settings.keymaps.toJSON(),
search: settings.search.toJSON(), search: settings.search.toJSON(),
properties: settings.properties.toJSON(), properties: settings.properties.toJSON(),
blacklist: settings.blacklist, blacklist: settings.blacklist.toJSON(),
}).to.deep.equal({ }).to.deep.equal({
keymaps: { keymaps: {
'j': { type: 'scroll.vertically', count: 1 }, 'j': { type: 'scroll.vertically', count: 1 },
@ -165,7 +166,7 @@ describe('shared/SettingData', () => {
smoothscroll: false, smoothscroll: false,
complete: "sbh" complete: "sbh"
}), }),
blacklist: [] blacklist: Blacklist.fromJSON([]),
}; };
let json = FormSettings.fromSettings(data).toJSON(); let json = FormSettings.fromSettings(data).toJSON();

@ -2,32 +2,6 @@ import * as settings from '../../src/shared/Settings';
import {expect} from 'chai'; import {expect} from 'chai';
describe('Settings', () => { describe('Settings', () => {
describe('#blacklistValueOf', () => {
it('returns empty array by empty settings', () => {
let blacklist = settings.blacklistValueOf([]);
expect(blacklist).to.be.empty;
});
it('returns blacklist by valid settings', () => {
let blacklist = settings.blacklistValueOf([
"github.com",
"circleci.com",
]);
expect(blacklist).to.deep.equal([
"github.com",
"circleci.com",
]);
});
it('throws a TypeError by invalid settings', () => {
expect(() => settings.blacklistValueOf(null)).to.throw(TypeError);
expect(() => settings.blacklistValueOf({})).to.throw(TypeError);
expect(() => settings.blacklistValueOf([1,2,3])).to.throw(TypeError);
});
});
describe('#valueOf', () => { describe('#valueOf', () => {
it('returns settings by valid settings', () => { it('returns settings by valid settings', () => {
let x = settings.valueOf({ let x = settings.valueOf({
@ -46,7 +20,7 @@ describe('Settings', () => {
keymaps: x.keymaps.toJSON(), keymaps: x.keymaps.toJSON(),
search: x.search.toJSON(), search: x.search.toJSON(),
properties: x.properties.toJSON(), properties: x.properties.toJSON(),
blacklist: x.blacklist, blacklist: x.blacklist.toJSON(),
}).to.deep.equal({ }).to.deep.equal({
keymaps: {}, keymaps: {},
search: { search: {
@ -70,7 +44,7 @@ describe('Settings', () => {
expect(value.properties.toJSON()).to.not.be.empty; expect(value.properties.toJSON()).to.not.be.empty;
expect(value.search.defaultEngine).to.be.a('string'); expect(value.search.defaultEngine).to.be.a('string');
expect(value.search.engines).to.be.an('object'); expect(value.search.engines).to.be.an('object');
expect(value.blacklist).to.be.empty; expect(value.blacklist.toJSON()).to.be.empty;
}); });
it('throws a TypeError with an unknown field', () => { it('throws a TypeError with an unknown field', () => {

@ -1,49 +0,0 @@
import { includes } from 'shared/blacklists';
describe("shared/blacklist", () => {
it('matches by *', () => {
let blacklist = ['*'];
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
})
it('matches by hostname', () => {
let blacklist = ['github.com'];
expect(includes(blacklist, 'https://github.com')).to.be.true;
expect(includes(blacklist, 'https://gist.github.com')).to.be.false;
expect(includes(blacklist, 'https://github.com/ueokande')).to.be.true;
expect(includes(blacklist, 'https://github.org')).to.be.false;
expect(includes(blacklist, 'https://google.com/search?q=github.org')).to.be.false;
})
it('matches by hostname with wildcard', () => {
let blacklist = ['*.github.com'];
expect(includes(blacklist, 'https://github.com')).to.be.false;
expect(includes(blacklist, 'https://gist.github.com')).to.be.true;
})
it('matches by path', () => {
let blacklist = ['github.com/abc'];
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.false;
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false;
})
it('matches by path with wildcard', () => {
let blacklist = ['github.com/abc*'];
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.true;
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false;
})
it('matches address and port', () => {
let blacklist = ['127.0.0.1:8888'];
expect(includes(blacklist, 'http://127.0.0.1:8888/')).to.be.true;
expect(includes(blacklist, 'http://127.0.0.1:8888/hello')).to.be.true;
})
});

@ -0,0 +1,77 @@
import Blacklist from '../../../src/shared/settings/Blacklist';
import { expect } from 'chai';
describe('Blacklist', () => {
describe('fromJSON', () => {
it('returns empty array by empty settings', () => {
let blacklist = Blacklist.fromJSON([]);
expect(blacklist.toJSON()).to.be.empty;
});
it('returns blacklist by valid settings', () => {
let blacklist = Blacklist.fromJSON([
'github.com',
'circleci.com',
]);
expect(blacklist.toJSON()).to.deep.equal([
'github.com',
'circleci.com',
]);
});
it('throws a TypeError by invalid settings', () => {
expect(() => Blacklist.fromJSON(null)).to.throw(TypeError);
expect(() => Blacklist.fromJSON({})).to.throw(TypeError);
expect(() => Blacklist.fromJSON([1,2,3])).to.throw(TypeError);
});
});
describe('#includes', () => {
it('matches by *', () => {
let blacklist = new Blacklist(['*']);
expect(blacklist.includes('https://github.com/abc')).to.be.true;
});
it('matches by hostname', () => {
let blacklist = new Blacklist(['github.com']);
expect(blacklist.includes('https://github.com')).to.be.true;
expect(blacklist.includes('https://gist.github.com')).to.be.false;
expect(blacklist.includes('https://github.com/ueokande')).to.be.true;
expect(blacklist.includes('https://github.org')).to.be.false;
expect(blacklist.includes('https://google.com/search?q=github.org')).to.be.false;
});
it('matches by hostname with wildcard', () => {
let blacklist = new Blacklist(['*.github.com']);
expect(blacklist.includes('https://github.com')).to.be.false;
expect(blacklist.includes('https://gist.github.com')).to.be.true;
})
it('matches by path', () => {
let blacklist = new Blacklist(['github.com/abc']);
expect(blacklist.includes('https://github.com/abc')).to.be.true;
expect(blacklist.includes('https://github.com/abcdef')).to.be.false;
expect(blacklist.includes('https://gist.github.com/abc')).to.be.false;
})
it('matches by path with wildcard', () => {
let blacklist = new Blacklist(['github.com/abc*']);
expect(blacklist.includes('https://github.com/abc')).to.be.true;
expect(blacklist.includes('https://github.com/abcdef')).to.be.true;
expect(blacklist.includes('https://gist.github.com/abc')).to.be.false;
})
it('matches address and port', () => {
let blacklist = new Blacklist(['127.0.0.1:8888']);
expect(blacklist.includes('http://127.0.0.1:8888/')).to.be.true;
expect(blacklist.includes('http://127.0.0.1:8888/hello')).to.be.true;
})
})
});

@ -1,19 +0,0 @@
import * as re from 'shared/utils/re';
describe("re util", () => {
it('matches by pattern', () => {
let regex = re.fromWildcard('*.example.com/*');
expect('foo.example.com/bar').to.match(regex);
expect('foo.example.com').not.to.match(regex);
expect('example.com/bar').not.to.match(regex);
regex = re.fromWildcard('example.com/*')
expect('example.com/foo').to.match(regex);
expect('example.com/').to.match(regex);
regex = re.fromWildcard('example.com/*bar')
expect('example.com/foobar').to.match(regex);
expect('example.com/bar').to.match(regex);
expect('example.com/foobarfoo').not.to.match(regex);
})
});