Repository: darkreader/darkreader (ts)

Issue:
Toggle button doesn't work on IPv6 addresses
Pressing the "Toggle" button on [::1]:3000 (ipv6 version of localhost:3000) has no effect. The only way to disable the extension is to press the "off" button which globally disables Dark Reader :(

Could you please make the toggling work with IPv6 loopback addresses?

Pull request description:
Add Ipv6 support
Resolved #197 

Patch:
diff --git a/src/background/user-storage.ts b/src/background/user-storage.ts
index 94e2b7f3..7c4139a2 100644
--- a/src/background/user-storage.ts
+++ b/src/background/user-storage.ts
@@ -93,6 +93,7 @@ export default class UserStorage {
                 let isOK = false;
                 try {
                     isURLMatched('https://google.com/', pattern);
+                    isURLMatched('[::1]:1337', pattern);
                     isOK = true;
                 } catch (err) {
                     console.warn(`Pattern "${pattern}" excluded`);
diff --git a/src/utils/ipv6.ts b/src/utils/ipv6.ts
new file mode 100644
index 00000000..e62509ba
--- /dev/null
+++ b/src/utils/ipv6.ts
@@ -0,0 +1,17 @@
+/**
+ * Compares if IPV6 addresses are the same.
+ * @param firstIP First IP to compare with
+ * @param secondIP Second IP to compare with
+ */
+export function compareIPV6(firstIP: string, secondIP: string) {
+    firstIP = (firstIP
+        .replace(/^\^/, '') // Remove ^ at start
+        .replace(/\$$/, '') // Remove $ at end
+        .replace(/^.*?\/{2,3}/, '') // Remove scheme
+        .replace(/\?.*$/, '') // Remove query
+        .replace(/\/$/, '') // Remove last slash
+    );
+    const ip = secondIP.toLowerCase();
+    const ip2 = firstIP.toLowerCase();
+    return ip === ip2;
+}
diff --git a/src/utils/url.ts b/src/utils/url.ts
index 51097df2..300735ee 100644
--- a/src/utils/url.ts
+++ b/src/utils/url.ts
@@ -1,4 +1,5 @@
 import {UserSettings} from '../definitions';
+import {compareIPV6} from './ipv6';
 
 export function getURLHost(url: string) {
     return url.match(/^(.*?\/{2,3})?(.+?)(\/|$)/)[2];
@@ -28,8 +29,16 @@ export function isURLInList(url: string, list: string[]) {
  * @param urlTemplate URL template ("google.*", "youtube.com" etc).
  */
 export function isURLMatched(url: string, urlTemplate: string): boolean {
-    const regex = createUrlRegex(urlTemplate);
-    return Boolean(url.match(regex));
+    const isFirstIPV6 = url.includes('[');
+    const isSecondIPV6 = urlTemplate.includes('[');
+    if (isFirstIPV6 && isSecondIPV6) {
+        return compareIPV6(url, urlTemplate);
+    } else if (!isFirstIPV6 && ! isSecondIPV6){
+        const regex = createUrlRegex(urlTemplate);
+        return Boolean(url.match(regex));
+    } else {
+        return false;
+    }
 }
 
 function createUrlRegex(urlTemplate: string): RegExp {


Test patch:
diff --git a/tests/benchmark-server/index.js b/tests/benchmark-server/index.js
index 20155710..b3130406 100644
--- a/tests/benchmark-server/index.js
+++ b/tests/benchmark-server/index.js
@@ -12,7 +12,7 @@ http.createServer((request, response) => {
         response.end();
         return;
     }
-    const type = query.includes('links') || query.includes('rules') || query.includes('styles')  ? 'index' : query.startsWith('generated') ? 'css' : '404';
+    const type = query.includes('links') || query.includes('rules') || query.includes('styles') ? 'index' : query.startsWith('generated') ? 'css' : '404';
     if (type === '404') {
         response.writeHead(404, {'Content-Type': 'text/plain'});
         response.write('404 Not Found\n');
@@ -29,12 +29,12 @@ http.createServer((request, response) => {
         const write = () => {
             const chunk = parts.unshift();
             if (chunk) {
-              response.write(chunk);
-              setTimeout(write, 10);
+                response.write(chunk);
+                setTimeout(write, 10);
             } else {
-              response.end();
+                response.end();
             }
-          };
+        };
         write();
     }
 
@@ -48,7 +48,7 @@ http.createServer((request, response) => {
  */
 function chunkString(str, length) {
     return str.match(new RegExp('.{1,' + length + '}', 'g'));
-  }
+}
 
 /**
  * Returns an generated CSS style
@@ -110,12 +110,12 @@ function rule(amount) {
     if (amount === 0) {
         return result;
     }
-    result = result + '\t\t\t<style>'
+    result = result + '\t\t\t<style>';
     for (let x = 1; x <= amount; x++) {
         result = result + `.GeneratedElement${x} { background-color: red }\n`;
         element = element + `\t\t<p class="GeneratedElement${x}">This is an GeneratedElement ${x}</p>\n`;
     }
-    result = result + '</style>\n'
+    result = result + '</style>\n';
     return [result, element];
 }
 
diff --git a/tests/url.tests.ts b/tests/url.tests.ts
index a0b78348..6b59afbe 100644
--- a/tests/url.tests.ts
+++ b/tests/url.tests.ts
@@ -125,6 +125,48 @@ test('URL is enabled', () => {
         'https://www.google.com/very/good/hidden/folder/pdf#file.pdf'
     )).toBe(false);
 
+    // IPV6 Testing
+    expect(isURLEnabled(
+        '[::1]:1337',
+        {siteList: ['google.com'], siteListEnabled: [], applyToListedOnly: false} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toBe(true);
+    expect(isURLEnabled(
+        '[::1]:8080',
+        {siteList: ['[::1]:8080'], siteListEnabled: [], applyToListedOnly: false} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toEqual(false);
+    expect(isURLEnabled(
+        '[::1]:8080',
+        {siteList: ['[::1]:8081'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toEqual(false);
+    expect(isURLEnabled(
+        '[::1]:8080',
+        {siteList: ['[::1]:8081'], siteListEnabled: [], applyToListedOnly: false} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toEqual(true);
+    expect(isURLEnabled(
+        '[::1]:17',
+        {siteList: ['[::1]'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toEqual(false);
+    expect(isURLEnabled(
+        '[2001:4860:4860::8888]',
+        {siteList: ['[2001:4860:4860::8888]'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toEqual(true);
+    expect(isURLEnabled(
+        '[2001:4860:4860::8844]',
+        {siteList: ['[2001:4860:4860::8844]'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
+        {isProtected: false, isInDarkList: true},
+    )).toEqual(true);
+    expect(isURLEnabled(
+        '[2001:4860:4860::8844]',
+        {siteList: [], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
+        {isProtected: false, isInDarkList: true},
+    )).toEqual(false);
+
     // Temporary Dark Sites list fix
     expect(isURLEnabled(
         'https://darkreader.org/',