浏览代码

feat: support magic TLD

close #214
Gerald 8 年之前
父节点
当前提交
3e2a2e5c1a
共有 3 个文件被更改,包括 78 次插入26 次删除
  1. 1 0
      package.json
  2. 49 26
      src/background/utils/tester.js
  3. 28 0
      test/background/tester.js

+ 1 - 0
package.json

@@ -69,6 +69,7 @@
   "dependencies": {
     "codemirror": "^5.29.0",
     "core-js": "^2.5.1",
+    "tldjs": "^2.2.0",
     "vue": "^2.4.2",
     "vueleton": "^0.4.0"
   }

+ 49 - 26
src/background/utils/tester.js

@@ -1,11 +1,13 @@
+import tldjs from 'tldjs';
 import cache from './cache';
 import { getOption, hookOptions } from './options';
 
-const RE = /(.*?):\/\/([^/]*)\/(.*)/;
+const RE_MATCH_PARTS = /(.*?):\/\/([^/]*)\/(.*)/;
 let blacklistRules = [];
 hookOptions(changes => {
   if ('blacklist' in changes) resetBlacklist(changes.blacklist || '');
 });
+const RE_HTTP_OR_HTTPS = /^https?$/i;
 
 /**
  * Test glob rules like `@include` and `@exclude`.
@@ -67,53 +69,74 @@ function mergeLists(...args) {
 }
 
 function str2RE(str) {
-  const re = str.replace(/([.?/])/g, '\\$1').replace(/\*/g, '.*?');
-  return RegExp(`^${re}$`);
+  const re = str.replace(/([.?])/g, '\\$1').replace(/\*/g, '.*?');
+  return `^${re}$`;
 }
 
 function autoReg(str) {
   if (str.length > 1 && str[0] === '/' && str[str.length - 1] === '/') {
-    return RegExp(str.slice(1, -1)); // Regular-expression
+    return new RegExp(str.slice(1, -1)); // Regular-expression
   }
-  const re = str2RE(str); // String with wildcards
-  return { test: tstr => re.test(tstr) };
+  const reStr = str2RE(str);
+  const re = new RegExp(reStr); // String with wildcards
+  const tests = [
+    tstr => re.test(tstr),
+  ];
+  if (str.includes('.tld/')) {
+    const reTldStr = reStr.replace('\\.tld/', '((?:\\.\\w+)+)/');
+    tests.push(tstr => {
+      const matches = tstr.match(reTldStr);
+      if (matches) {
+        const suffix = matches[1].slice(1);
+        if (tldjs.getPublicSuffix(suffix) === suffix) return true;
+      }
+      return false;
+    });
+  }
+  return { test: tstr => tests.some(test => test(tstr)) };
 }
 
 function matchScheme(rule, data) {
   // exact match
   if (rule === data) return 1;
   // * = http | https
-  if (rule === '*' && /^https?$/i.test(data)) return 1;
+  if (rule === '*' && RE_HTTP_OR_HTTPS.test(data)) return 1;
   return 0;
 }
-function matchHost(rule, data) {
-  // * matches all
-  if (rule === '*') return 1;
-  // exact match
-  if (rule === data) return 1;
-  // *.example.com
-  if (/^\*\.[^*]*$/.test(rule)) {
-    // matches the specified domain
-    if (rule.slice(2) === data) return 1;
-    // matches subdomains
-    if (str2RE(rule).test(data)) return 1;
-  }
-  return 0;
+function hostMatcher(rule) {
+  const reRule = new RegExp(str2RE(rule));
+  return data => {
+    // * matches all
+    if (rule === '*') return 1;
+    // exact match
+    if (rule === data) return 1;
+    // *.example.com
+    if (/^\*\.[^*]*$/.test(rule)) {
+      // matches the specified domain
+      if (rule.slice(2) === data) return 1;
+      // matches subdomains
+      if (reRule.test(data)) return 1;
+    }
+    return 0;
+  };
 }
-function matchPath(rule, data) {
-  return str2RE(rule).test(data);
+function pathMatcher(rule) {
+  const reRule = new RegExp(str2RE(rule));
+  return data => reRule.test(data);
 }
 function matchTester(rule) {
   let test;
   if (rule === '<all_urls>') test = () => true;
   else {
-    const ruleParts = rule.match(RE);
+    const ruleParts = rule.match(RE_MATCH_PARTS);
+    const matchHost = hostMatcher(ruleParts[2]);
+    const matchPath = pathMatcher(ruleParts[3]);
     test = url => {
-      const parts = url.match(RE);
+      const parts = url.match(RE_MATCH_PARTS);
       return !!ruleParts && !!parts
       && matchScheme(ruleParts[1], parts[1])
-      && matchHost(ruleParts[2], parts[2])
-      && matchPath(ruleParts[3], parts[3]);
+      && matchHost(parts[2])
+      && matchPath(parts[3]);
     };
   }
   return { test };

+ 28 - 0
test/background/tester.js

@@ -141,6 +141,20 @@ test('include', t => {
     q.notOk(testScript('https://www.hello.com/', script), 'not include by prefix');
     q.end();
   });
+
+  t.test('should support magic TLD', q => {
+    const script = buildScript({
+      meta: {
+        include: [
+          'https://www.google.tld/*',
+        ],
+      },
+    });
+    q.ok(testScript('https://www.google.com/', script), 'should match `.com`');
+    q.ok(testScript('https://www.google.com.hk/', script), 'should match `.com.hk`');
+    q.notOk(testScript('https://www.google.example.com/', script), 'should not match subdomains');
+    q.end();
+  });
 });
 
 test('exclude', t => {
@@ -176,6 +190,20 @@ test('exclude', t => {
     q.ok(testScript('https://www.hello.com/', script), 'not exclude by prefix');
     q.end();
   });
+
+  t.test('should support magic TLD', q => {
+    const script = buildScript({
+      meta: {
+        exclude: [
+          'https://www.google.tld/*',
+        ],
+      },
+    });
+    q.notOk(testScript('https://www.google.com/', script), 'should match `.com`');
+    q.notOk(testScript('https://www.google.com.hk/', script), 'should match `.com.hk`');
+    q.ok(testScript('https://www.google.example.com/', script), 'should not match subdomains');
+    q.end();
+  });
 });
 
 test('exclude-match', t => {