From 62fb6e4450afb3937679de7af863a5c380b50edb Mon Sep 17 00:00:00 2001 From: zhuzhuor Date: Thu, 30 Jun 2022 23:54:16 -0700 Subject: [PATCH] Rewriting the chrome extension with manifest v3 Currently: * The background page and popup page seem to be working fine * Cleaned up a lot of code and rewrote them to use more morden JS features like async/await/modules * Restructured the folder structure to make more sense; and reformatted some files to use a better coding style Still need to: * Fix the options page for setting custom proxy server * Make the header modifieer work with manifest v3 * Change the remaining files to comply with the code style --- .eslintrc.js | 20 + .eslintrc.json | 17 - Procfile | 2 - README.md | 6 +- chrome/config.js | 258 -------- chrome/donation.js | 32 - chrome/header.js | 65 -- chrome/pages/LICENSE.txt | 1 - chrome/pages/js/analytics.js | 94 --- chrome/pages/js/options-proxy.js | 174 ------ chrome/pages/js/popup.js | 83 --- chrome/proxy.js | 86 --- chrome/redirect.js | 65 -- chrome/storage.js | 42 -- {chrome/icons => icons}/icon128.png | Bin {chrome/icons => icons}/icon16.png | Bin {chrome/icons => icons}/icon19.png | Bin {chrome/icons => icons}/icon19gray.png | Bin {chrome/icons => icons}/icon19heart.png | Bin {chrome/icons => icons}/icon19spring.png | Bin {chrome/icons => icons}/icon19xmas.png | Bin {chrome/icons => icons}/icon48.png | Bin manifest.json | 81 +-- package.json | 29 +- server/server.js | 269 --------- server/utils.js | 260 -------- shared/produce_regex.js | 9 - shared/tools.js | 296 --------- shared/urls.js | 563 ------------------ src/background.mjs | 17 + src/configs/servers.mjs | 4 + src/configs/urls.mjs | 329 ++++++++++ .../content_scripts}/music.163.js | 0 .../content_scripts}/play.baidu.css | 0 .../content_scripts}/play.baidu.js | 0 .../content => src/content_scripts}/tudou.js | 0 .../content_scripts}/unblockcn.js | 0 {chrome/pages => src}/css/popup.css | 29 +- src/modules/_header.mjs | 57 ++ src/modules/_icon.mjs | 75 +++ src/modules/_proxy.mjs | 70 +++ src/modules/_storage.mjs | 25 + src/modules/_url_utils.mjs | 238 ++++++++ src/modules/modes.mjs | 5 + src/modules/settings.mjs | 120 ++++ {chrome/pages => src}/options.html | 21 +- src/options.mjs | 108 ++++ {chrome/pages => src}/popup.html | 25 +- src/popup.mjs | 62 ++ .../third_party}/css/bootstrap-3.3.5.min.css | 0 .../css/font-awesome-4.4.0.min.css | 0 .../fonts/fontawesome-webfont.woff2 | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../third_party}/js/bootstrap-3.3.5.min.js | 0 .../third_party}/js/jquery-2.1.4.min.js | 0 tools/produce_regex.mjs | 48 ++ 56 files changed, 1236 insertions(+), 2449 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json delete mode 100644 Procfile delete mode 100644 chrome/config.js delete mode 100644 chrome/donation.js delete mode 100644 chrome/header.js delete mode 100644 chrome/pages/LICENSE.txt delete mode 100644 chrome/pages/js/analytics.js delete mode 100644 chrome/pages/js/options-proxy.js delete mode 100644 chrome/pages/js/popup.js delete mode 100644 chrome/proxy.js delete mode 100644 chrome/redirect.js delete mode 100644 chrome/storage.js rename {chrome/icons => icons}/icon128.png (100%) rename {chrome/icons => icons}/icon16.png (100%) rename {chrome/icons => icons}/icon19.png (100%) rename {chrome/icons => icons}/icon19gray.png (100%) rename {chrome/icons => icons}/icon19heart.png (100%) rename {chrome/icons => icons}/icon19spring.png (100%) rename {chrome/icons => icons}/icon19xmas.png (100%) rename {chrome/icons => icons}/icon48.png (100%) delete mode 100755 server/server.js delete mode 100644 server/utils.js delete mode 100644 shared/produce_regex.js delete mode 100644 shared/tools.js delete mode 100644 shared/urls.js create mode 100644 src/background.mjs create mode 100644 src/configs/servers.mjs create mode 100644 src/configs/urls.mjs rename {chrome/content => src/content_scripts}/music.163.js (100%) rename {chrome/content => src/content_scripts}/play.baidu.css (100%) rename {chrome/content => src/content_scripts}/play.baidu.js (100%) rename {chrome/content => src/content_scripts}/tudou.js (100%) rename {chrome/content => src/content_scripts}/unblockcn.js (100%) rename {chrome/pages => src}/css/popup.css (50%) create mode 100644 src/modules/_header.mjs create mode 100644 src/modules/_icon.mjs create mode 100644 src/modules/_proxy.mjs create mode 100644 src/modules/_storage.mjs create mode 100644 src/modules/_url_utils.mjs create mode 100644 src/modules/modes.mjs create mode 100644 src/modules/settings.mjs rename {chrome/pages => src}/options.html (76%) create mode 100644 src/options.mjs rename {chrome/pages => src}/popup.html (78%) create mode 100644 src/popup.mjs rename {chrome/pages => src/third_party}/css/bootstrap-3.3.5.min.css (100%) rename {chrome/pages => src/third_party}/css/font-awesome-4.4.0.min.css (100%) rename {chrome/pages => src/third_party}/fonts/fontawesome-webfont.woff2 (100%) rename {chrome/pages => src/third_party}/fonts/glyphicons-halflings-regular.woff2 (100%) rename {chrome/pages => src/third_party}/js/bootstrap-3.3.5.min.js (100%) rename {chrome/pages => src/third_party}/js/jquery-2.1.4.min.js (100%) create mode 100644 tools/produce_regex.mjs diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..6746346b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + env: { + es2021: true, + browser: true, + webextensions: true, + node: true, + jquery: true, + }, + extends: [ + 'google', + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + 'require-jsdoc': 'off', + 'max-len': ['error', {'code': 100}], + }, +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index b211b9d3..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "env": { - "browser": true, - "webextensions": true, - "node": true, - "jquery": true, - "es2021": true - }, - "extends": [ - "eslint:recommended" - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": {} -} \ No newline at end of file diff --git a/Procfile b/Procfile deleted file mode 100644 index 4ebe101d..00000000 --- a/Procfile +++ /dev/null @@ -1,2 +0,0 @@ -web: node server/server.js --nolog - diff --git a/README.md b/README.md index 509334d1..8e7f6416 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # Unblock Youku -This project contains two parts (closely related with shared JavaScript code). - -1. A _Google Chrome extension_ helping users access their web services while travelling outside mainland China. You can find this extension on Chrome Web Store at [http://uku.im/chrome](http://uku.im/chrome). - -2. A Node.js based _backend server_ that supports the redirect mode of the Chrome extension. [![Build Status](https://travis-ci.org/uku/Unblock-Youku.svg?branch=master)](https://travis-ci.org/uku/Unblock-Youku) + A Chrome extension helping users access their web services while travelling outside mainland China. You can find this extension on Chrome Web Store at [http://uku.im/chrome](http://uku.im/chrome). ## Disclaimer diff --git a/chrome/config.js b/chrome/config.js deleted file mode 100644 index 20b1b0e7..00000000 --- a/chrome/config.js +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/*global setup_header: false, setup_proxy: false */ -/*global clear_redirect: false, clear_header: false, clear_proxy: false */ -/*global ga_report_event: false, ga_report_ratio: false, ga_report_error: false */ -"use strict"; - -// ====== Constant and Variable Settings ====== -var unblock_youku = unblock_youku || {}; // namespace - -// Default proxy server settings -unblock_youku.default_proxy_server_proc = 'HTTPS'; -unblock_youku.default_proxy_server_addr = 'secure.uku.im:8443'; -unblock_youku.backup_proxy_server_proc = 'HTTPS'; -unblock_youku.backup_proxy_server_addr = 'secure.uku.im:993'; - - -unblock_youku.ip_addr = new_random_ip(); -console.log('generated random ip addr: ' + unblock_youku.ip_addr); - - -// ====== Configuration Functions ====== -function set_mode_name(mode_name, callback) { - if (typeof callback === 'undefined') { - var err_msg = 'missing callback function in set_mode_name()'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - } - - if (mode_name === 'off') { - set_storage('unblock_youku_mode', mode_name, callback); - } else { - set_storage('unblock_youku_mode', 'normal', callback); - } -} - -function get_mode_name(callback) { - if (typeof callback === 'undefined') { - var err_msg = 'missing callback function in get_mode_name()'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - } - - get_storage('unblock_youku_mode', function(current_mode) { - if (typeof current_mode === 'undefined' || current_mode !== 'off') { - set_mode_name('normal', function () { - callback('normal'); - }); - } else { - callback(current_mode); - } - }); -} - -function clear_mode_settings(mode_name) { - switch (mode_name) { - case 'off': - console.log('cleared settings for off'); - break; - case 'lite': - clear_header(); - clear_redirect(); - console.log('cleared settings for lite'); - break; - case 'normal': - clear_header(); - clear_redirect(); - clear_proxy(); - console.log('cleared settings for normal'); - break; - default: - var err_msg = 'clear_mode_settings: should never come here'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - break; - } - - console.log('cleared the settings for the mode: ' + mode_name); -} - -function setup_mode_settings(mode_name) { - switch (mode_name) { - case 'off': - chrome.browserAction.setBadgeText({ text: 'OFF' }); - chrome.browserAction.setTitle({ title: 'Unblock Youku has been turned off.' }); - change_browser_icon('off'); - break; - case 'lite': - set_mode_name('normal', function() { - console.log('migrate lite mode to normal mode'); - }); - // fall through - case 'normal': - setup_header(); - setup_proxy(); - chrome.browserAction.setBadgeText({ text: '' }); - change_browser_icon('normal'); - break; - default: - var err_msg = 'setup_mode_settings: should never come here'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - break; - } - - console.log('initialized the settings for the mode: ' + mode_name); -} - -function change_mode(new_mode_name) { - set_mode_name(new_mode_name, function() {}); - // the storage change listener would take care about the setting changes -} - -function change_browser_icon(option) { - function _change_browser_icon(option) { - var today = new Date(); - var y = today.getFullYear(); - var d = today.getDate(); - var m = today.getMonth() + 1; - - // hard-coded spring festival dates - var is_spring = false; - switch (y) { - case 2023: // Jan. 22, 2023 - is_spring = m === 1; - break; - case 2024: // Feb. 10, 2024 - is_spring = m === 2; - break; - case 2025: // Jan. 29, 2025 - is_spring = m === 1; - break; - case 2026: // Feb. 17, 2026 - is_spring = m === 2; - break; - case 2027: // Feb. 7, 2027 - is_spring = m === 2; - break; - } - if (is_spring) { - chrome.browserAction.setIcon({path: 'chrome/icons/icon19spring.png'}); - chrome.browserAction.setTitle({title: 'Happy Spring Festival! (Unblock Youku ' + unblock_youku.version + ')'}); - return; - } - - // christmas - if (m === 12 && d >= 15) { - chrome.browserAction.setIcon({path: 'chrome/icons/icon19xmas.png'}); - chrome.browserAction.setTitle({title: 'Merry Christmas! (Unblock Youku ' + unblock_youku.version + ')'}); - return; - } - - if (option === 'off') { - chrome.browserAction.setIcon({path: 'chrome/icons/icon19gray.png'}); - return; - } - - chrome.browserAction.setIcon({path: 'chrome/icons/icon19.png'}); - chrome.browserAction.setTitle({title: 'Unblock Youku ' + unblock_youku.version}); - } - - // check chrome.storage before changing icons - // the mode should already be set in previous get_mode_name() - get_storage('unblock_youku_mode', function(current_mode) { - if (typeof current_mode !== 'undefined') { - _change_browser_icon(option); - } else { - var err_msg = 'chrome.storage has some problems'; - console.log(err_msg); - ga_report_error('Unexpected Error', err_msg); - } - }); -} - - -// Settings are changed asynchronously -function storage_monitor(changes, area) { - console.log('storage changes: ' + JSON.stringify(changes)); - - if (typeof changes.unblock_youku_mode !== 'undefined') { - var mode_change = changes.unblock_youku_mode; - - // doesn't run if it's first time to migrate the old settings - if (typeof mode_change.oldValue !== 'undefined' && typeof mode_change.newValue !== 'undefined') { - clear_mode_settings(mode_change.oldValue); - setup_mode_settings(mode_change.newValue); - ga_report_event('Change Mode', mode_change.oldValue + ' -> ' + mode_change.newValue); - } - } - - if (typeof changes.custom_proxy_server !== 'undefined') { - var proxy_server_change = changes.custom_proxy_server; - if (typeof proxy_server_change.newValue !== 'undefined' - && typeof proxy_server_change.newValue.proc !== 'undefined' - && typeof proxy_server_change.newValue.addr !== 'undefined') { - localStorage.custom_proxy_server_proc = proxy_server_change.newValue.proc; - localStorage.custom_proxy_server_addr = proxy_server_change.newValue.addr; - } else { - if (typeof localStorage.custom_proxy_server_proc !== 'undefined') { - localStorage.removeItem('custom_proxy_server_proc'); - } - if (typeof localStorage.custom_proxy_server_addr !== 'undefined') { - localStorage.removeItem('custom_proxy_server_addr'); - } - } - } -} - - -function setup_storage_monitor() { - if (!chrome.storage.onChanged.hasListener(storage_monitor)) { - chrome.storage.onChanged.addListener(storage_monitor); - console.log('storage_monitor is set'); - } else { - var err_msg = 'storage_monitor is already there!'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - } -} - - -// ====== Initialization ====== -document.addEventListener("DOMContentLoaded", function() { - setup_storage_monitor(); - - unblock_youku.version = chrome.runtime.getManifest().version; - - // the latest version to show NEW on the icon; it's usually a big update with new features - unblock_youku.lastest_new_version = '3.0.0.1'; - get_storage('previous_new_version', function(version) { - // previous_new_version will be set by the popup page once the page is opened - if (typeof version === 'undefined' || version !== unblock_youku.lastest_new_version) { - chrome.browserAction.setBadgeText({text: 'NEW'}); - } - }); - - get_mode_name(function(current_mode_name) { - setup_mode_settings(current_mode_name); - - ga_report_ratio('Init Mode', current_mode_name); - ga_report_ratio('Version', unblock_youku.version); - }); -}); diff --git a/chrome/donation.js b/chrome/donation.js deleted file mode 100644 index aa16dd5d..00000000 --- a/chrome/donation.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -'use strict'; - - -function create_donation_tab() { - const donation_url = chrome.i18n.getMessage('donation_url'); - chrome.tabs.create({ - url: donation_url, - }); -} - -chrome.runtime.onInstalled.addListener(function(details) { - if (details.reason === 'install') { - create_donation_tab(); - } -}); diff --git a/chrome/header.js b/chrome/header.js deleted file mode 100644 index b42cb588..00000000 --- a/chrome/header.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/*global unblock_youku: false */ -/*global ga_report_error: false */ -"use strict"; - - -function header_modifier(details) { - console.log('modify headers of ' + details.url); - - details.requestHeaders.push({ - name: 'X-Forwarded-For', - value: unblock_youku.ip_addr - }, { - name: 'Client-IP', - value: unblock_youku.ip_addr - }); - - return {requestHeaders: details.requestHeaders}; -} - - -function setup_header() { - if (!chrome.webRequest.onBeforeSendHeaders.hasListener(header_modifier)) { - chrome.webRequest.onBeforeSendHeaders.addListener( - header_modifier, - { - urls: unblock_youku.header_urls - }, - ['requestHeaders', 'blocking'] - ); - console.log('header_modifier is set'); - } else { - var err_msg = 'header_modifer is already there!'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - } -} - - -function clear_header() { - if (chrome.webRequest.onBeforeSendHeaders.hasListener(header_modifier)) { - chrome.webRequest.onBeforeSendHeaders.removeListener(header_modifier); - console.log('header_modifier is removed'); - } else { - var err_msg = 'header_modifier is not there!'; - console.error(err_msg); - ga_report_error('Unexpected Error', err_msg); - } -} diff --git a/chrome/pages/LICENSE.txt b/chrome/pages/LICENSE.txt deleted file mode 100644 index 811d471e..00000000 --- a/chrome/pages/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -The libraries, including Bootstrap and jQuery, contained in this folder are under their own licenses. diff --git a/chrome/pages/js/analytics.js b/chrome/pages/js/analytics.js deleted file mode 100644 index ae1b7973..00000000 --- a/chrome/pages/js/analytics.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2012 - 2014 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - -/*jslint browser: true */ - -var unblock_youku = unblock_youku || {}; - - -(function() { - "use strict"; - if (typeof localStorage.uuid === 'undefined') { - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript - unblock_youku.uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random()*16|0; - var v = c === 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }); - localStorage.uuid = unblock_youku.uuid; - } else { - unblock_youku.uuid = localStorage.uuid; - } -}()); - - -function ga_collect_data(type, data) { - "use strict"; - var xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://www.google-analytics.com/collect', true); - var payload = 'v=1&' - + 'tid=UA-30726750-8&' - + 'cid='+ unblock_youku.uuid + '&' - + 'aip=1&' // anonymize IP - + 't=' + type + '&' - + data; - xhr.send(payload); -} - - -function ga_report_event(event_name, event_desc, collection_rate) { - "use strict"; - if (typeof collection_rate !== 'undefined' && Math.random() > collection_rate) { - // reduce data points - return; - } - - var data = 'ec=' + encodeURIComponent(event_name) + '&' - + 'ea=' + encodeURIComponent(event_desc); - ga_collect_data('event', data); -} - - -function ga_report_ratio(ratio_name, ratio_value) { - ga_report_event(ratio_name, ratio_value, 0.1); - // ga_report_event(ratio_name, ratio_value, 1.0); -} - - -function ga_report_timeout(timeout_type, timeout_server) { - ga_report_event(timeout_type, timeout_server, 0.1); -} - - -function ga_report_error(error_name, error_desc) { - "use strict"; - // var data = 'exd=' + encodeURIComponent(error_name) + '&' - // + 'exf=' + encodeURIComponent(error_desc); - // ga_collect_data('exception', data); - ga_report_event(error_name, error_desc); -} - - - -// see http://goo.gl/QLJu6 and http://goo.gl/aNH3H -window.onerror = function(message, file, line) { - "use strict"; - var msg = file + '(' + line + '): ' + message; - // console.error(msg); - ga_report_error('Unknown Error', msg); -}; diff --git a/chrome/pages/js/options-proxy.js b/chrome/pages/js/options-proxy.js deleted file mode 100644 index d1afc14d..00000000 --- a/chrome/pages/js/options-proxy.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -var background = background || chrome.extension.getBackgroundPage(); -var default_proxy_server_proc = background.unblock_youku.default_proxy_server_proc; -var default_proxy_server_addr = background.unblock_youku.default_proxy_server_addr; - -function show_proxy_message(type, content) { - "use strict"; - var alert_type = 'info'; - if (type === 'success' || type === 'warning') { - alert_type = type; // success, info, or warning - } - - $('#proxy_message').html('
' + content + '
'); -} - -function show_proxy_error(content) { - "use strict"; - $('#proxy_message').html('
' + content + '
'); -} - -function show_proxy_test_message(type, content) { - "use strict"; - var alert_type = 'info'; - if (type === 'success' || type === 'warning' || type === 'danger') { - alert_type = type; // success, info, or warning - } - - $('#proxy_test_message').html('
' + content + '
'); -} - -function remove_custom_proxy_server(callback) { - "use strict"; - // Change localStorage first to make sure it contains the correct values before updating PAC - background.localStorage.removeItem('custom_proxy_server_proc'); - background.localStorage.removeItem('custom_proxy_server_addr'); - background.remove_storage('custom_proxy_server', callback); -} - -function set_custom_proxy_server(server_proc, server_addr, callback) { - "use strict"; - background.localStorage.custom_proxy_server_proc = server_proc; - background.localStorage.custom_proxy_server_addr = server_addr; - background.set_storage("custom_proxy_server", { - proc : server_proc, - addr : server_addr - }, callback); -} - -function get_custom_proxy_server(callback) { - "use strict"; - background.get_storage('custom_proxy_server', function(server_info) { - if (typeof server_info === 'undefined' - || typeof server_info.proc === 'undefined' - || typeof server_info.addr === 'undefined') { - callback(/*custom_enabled=*/false, default_proxy_server_proc, default_proxy_server_addr); - } else { - callback(/*custom_enabled=*/true, server_info.proc, server_info.addr); - } - }); -} - -function test_custom_proxy_server(callback) { - "use strict"; - // TODO: Change this URL to a new one. It stopped working a while ago. - var test_url = 'http://ipservice.163.com/isFromMainland'; - show_proxy_test_message('info', 'Testing connection & Unblock...'); - $.get(test_url, function(data) { - if (data === 'true') { - show_proxy_test_message('success', 'Unblock OK.'); - } else { - show_proxy_test_message('danger', 'Unblock test failed! Invalid config or server not located in mainland China.'); - } - }).error(function() { - show_proxy_test_message('danger', 'Unblock test failed! Invalid config or server not working properly.'); - }); -} - -$(document).ready(function() { - "use strict"; - get_custom_proxy_server(function(custom_enabled, server_proc, server_addr) { - $('#custom_proxy_proc option').each(function(idx, d) { - if (d.value === server_proc) { - d.selected = true; - } - }); - $('#custom_proxy_addr').val(server_addr); - if (custom_enabled === true) { - $('#custom_proxy_proc').attr('disabled', true); - $('#custom_proxy_addr').attr('disabled', true); - $('#custom_proxy_enable').attr('disabled', true); - background.get_mode_name(function(current_mode) { - if (current_mode === 'normal') { - show_proxy_message('info', 'Status: Enabled'); - } else { - show_proxy_message('warning', 'Status: Enabled. But current mode is not proxy mode.'); - } - }); - } else { - show_proxy_message('info', 'Status: NOT Enabled'); - $('#custom_proxy_reset').attr('disabled', true); - } - }); - - $('#custom_proxy_enable').click(function() { - var custom_proxy_proc = $('#custom_proxy_proc').val(); - var custom_proxy_addr = $('#custom_proxy_addr').val(); - $('#custom_proxy_proc').attr('disabled', true); - $('#custom_proxy_addr').attr('disabled', true); - $('#custom_proxy_enable').attr('disabled', true); - set_custom_proxy_server(custom_proxy_proc, custom_proxy_addr, function() { - // switch to proxy mode and force refresh pac proxy - background.get_mode_name(function(current_mode) { - if (current_mode === 'normal') { - background.setup_proxy(); - $('#custom_proxy_reset').attr('disabled', false); - show_proxy_message('info', 'Enabled custom proxy server.'); - // test_custom_proxy_server(); - } else { - background.set_mode_name('normal', function() { - $('#custom_proxy_reset').attr("disabled", false); - show_proxy_message('warning', 'Enabled custom proxy server, and changed to proxy mode.'); - // test_custom_proxy_server(); - }); - } - }); - }); - }); - - $('#custom_proxy_reset').click(function() { - $("#custom_proxy_reset").attr('disabled', true); - remove_custom_proxy_server(function() { - // switch to proxy mode and force refresh pac proxy - background.get_mode_name(function(current_mode) { - if (current_mode === 'normal') { - background.setup_proxy(); - $('#custom_proxy_proc').attr('disabled', false); - $('#custom_proxy_addr').attr('disabled', false); - $('#custom_proxy_enable').attr('disabled', false); - show_proxy_message('warning', 'Reset custom proxy server.'); - // test_custom_proxy_server(); - } else { - background.set_mode_name('normal', function() { - $('#custom_proxy_proc').attr('disabled', false); - $('#custom_proxy_addr').attr('disabled', false); - $('#custom_proxy_enable').attr('disabled', false); - show_proxy_message('warning', 'Reset custom proxy server, and changed to proxy mode.'); - // test_custom_proxy_server(); - }); - } - }); - }); - }); - - $('#form_custom_proxy_server').submit(function(event) { - // prevent the default action of submitting a form - event.preventDefault(); - }); -}); diff --git a/chrome/pages/js/popup.js b/chrome/pages/js/popup.js deleted file mode 100644 index c068f441..00000000 --- a/chrome/pages/js/popup.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - -function set_i18n_text() { - "use strict"; - var get_msg = chrome.i18n.getMessage; - - $('div#support strong').html(get_msg('support_title')); - $('p#support_message').html(get_msg('support_message')); - $('a#support_link').attr('href', get_msg('donation_url')); - $('button#support_button').html(get_msg('support_button')); - - $('div#social strong').html(get_msg('social_title')); - - $('div#mode_select strong').html(get_msg('mode_select')); - - $('span.mode_off_name').html(get_msg('mode_off')); - $('span.mode_off_desc').html(get_msg('mode_off_description')); - $('span.mode_normal_name').html(get_msg('mode_normal')); - $('span.mode_normal_desc').html(get_msg('mode_normal_description')); - - $('div#faq').html(get_msg('faq')); - $('div#feedback').html(get_msg('feedback')); - $('div#rating').html(get_msg('rating')); -} - -$(document).ready(function() { - "use strict"; - set_i18n_text(); - - var background = chrome.extension.getBackgroundPage(); - - // set default button display - background.get_mode_name(function(current_mode_name) { - switch (current_mode_name) { - case 'off': - $('label#off').addClass('active'); - break; - default: - $('label#normal').addClass('active'); - break; - } - }); - - - chrome.browserAction.setBadgeText({text: ''}); // clear the text NEW - background.get_storage('previous_new_version', function(version) { - if (typeof version === 'undefined' || version !== background.unblock_youku.lastest_new_version) { - background.set_storage('previous_new_version', background.unblock_youku.lastest_new_version); - } - }); - - $('div#version small').html('Unblock Youku v' + background.unblock_youku.version); - - // button actions - $('input#input_off').change(function() { - console.log('to change mode to off'); - background.change_mode('off'); - }); - $('input#input_normal').change(function() { - console.log('to change mode to normal'); - background.change_mode('normal'); - }); - - // enable tooltip - $('#tooltip').tooltip(); -}); - diff --git a/chrome/proxy.js b/chrome/proxy.js deleted file mode 100644 index b60430ff..00000000 --- a/chrome/proxy.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/*jshint devel:true, globalstrict: true */ -/*global chrome: false, unblock_youku: false, new_sogou_proxy_addr: false, urls2pac: false, get_mode_name: false */ -/*global ga_report_timeout: false, ga_report_error: false, ga_report_event: false, localStorage: false */ -"use strict"; - -function setup_proxy() { - function setup_pac_data(proxy_prot_1, proxy_addr_1, - proxy_prot_2, proxy_addr_2) { - var pac_data = urls2pac( - unblock_youku.chrome_proxy_bypass_urls, - unblock_youku.chrome_proxy_urls, - proxy_addr_1, proxy_prot_1, - proxy_addr_2, proxy_prot_2); - // console.log(pac_data); - var proxy_config = { - mode: 'pac_script', - pacScript: { - data: pac_data - } - }; - chrome.proxy.settings.set( - { - value: proxy_config, - scope: 'regular' - }, - function() {} - ); - } - - - console.group('to set up proxy'); - - var proxy_server_proc = unblock_youku.default_proxy_server_proc; - var proxy_server_addr = unblock_youku.default_proxy_server_addr; - var backup_proxy_server_proc = unblock_youku.backup_proxy_server_proc; - var backup_proxy_server_addr = unblock_youku.backup_proxy_server_addr; - - if (typeof localStorage.custom_proxy_server_proc !== 'undefined' && - typeof localStorage.custom_proxy_server_addr !== 'undefined') { - proxy_server_proc = localStorage.custom_proxy_server_proc; - proxy_server_addr = localStorage.custom_proxy_server_addr; - backup_proxy_server_proc = localStorage.custom_proxy_server_proc; - backup_proxy_server_addr = localStorage.custom_proxy_server_addr; - } - - setup_pac_data(proxy_server_proc, proxy_server_addr, - backup_proxy_server_proc, backup_proxy_server_addr); - console.log('using the proxy server: ' + proxy_server_proc + ' ' + proxy_server_addr); - ga_report_event('Proxy Server Selection', proxy_server_proc + ' ' + proxy_server_addr, 0.1); - - console.groupEnd(); -} - - -function clear_proxy() { - var proxy_config = { - mode: 'system' - }; - - chrome.proxy.settings.set( - { - value: proxy_config, - scope: 'regular' - }, - function() {} - ); - - console.log('proxy is removed (changed to system setting)'); -} diff --git a/chrome/redirect.js b/chrome/redirect.js deleted file mode 100644 index 2f6af78f..00000000 --- a/chrome/redirect.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/*global string_starts_with: false */ -"use strict"; - - -function http_redirector(details) { - console.log('original url: ' + details.url); - if (details.url.slice(-15) === 'crossdomain.xml') { - console.log('directly pass'); - return {}; - } - var redirect_url = null; - - // special treatment for play.baidu - if (details.url.slice(0, 41) === 'http://play.baidu.com/data/music/songlink') { - redirect_url = 'http://play.baidu.com/data/cloud/songlink' + details.url.slice(41); - console.log('redirect url: ' + redirect_url); - return {redirectUrl: redirect_url}; - } - - var backend_server; - if (typeof localStorage.custom_redirect_server === 'undefined') { - backend_server = unblock_youku.actual_redirect_server; - } else { - backend_server = localStorage.custom_redirect_server; - } - - if (string_starts_with(details.url, 'http://')) { - redirect_url = 'http://' + backend_server + '/http/' + details.url.substring('http://'.length); - } else if (string_starts_with(details.url, 'https://')) { - redirect_url = 'http://' + backend_server + '/https/' + details.url.substring('https://'.length); - } - console.log('redirect url: ' + redirect_url); - - if (redirect_url !== null) { - return {redirectUrl: redirect_url}; - } - return {}; -} - - -function clear_redirect() { - if (chrome.webRequest.onBeforeRequest.hasListener(http_redirector)) { - chrome.webRequest.onBeforeRequest.removeListener(http_redirector); - console.log('http_redirector is removed'); - } else { - console.log('http_redirector is not there!'); - } -} diff --git a/chrome/storage.js b/chrome/storage.js deleted file mode 100644 index 6856e79d..00000000 --- a/chrome/storage.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/*global chrome: false */ -"use strict"; - -// have to use a callback function -function get_storage(key, callback) { - chrome.storage.sync.get(key, function(items) { - if (typeof items !== 'undefined' && items.hasOwnProperty(key)) { - callback(items[key]); - } else { - callback(); - } - }); -} - - -function set_storage(key, value, callback) { - var obj = {}; - obj[key] = value; // can't just use {key: value} - chrome.storage.sync.set(obj, callback); -} - - -function remove_storage(key, callback) { - chrome.storage.sync.remove(key, callback); -} \ No newline at end of file diff --git a/chrome/icons/icon128.png b/icons/icon128.png similarity index 100% rename from chrome/icons/icon128.png rename to icons/icon128.png diff --git a/chrome/icons/icon16.png b/icons/icon16.png similarity index 100% rename from chrome/icons/icon16.png rename to icons/icon16.png diff --git a/chrome/icons/icon19.png b/icons/icon19.png similarity index 100% rename from chrome/icons/icon19.png rename to icons/icon19.png diff --git a/chrome/icons/icon19gray.png b/icons/icon19gray.png similarity index 100% rename from chrome/icons/icon19gray.png rename to icons/icon19gray.png diff --git a/chrome/icons/icon19heart.png b/icons/icon19heart.png similarity index 100% rename from chrome/icons/icon19heart.png rename to icons/icon19heart.png diff --git a/chrome/icons/icon19spring.png b/icons/icon19spring.png similarity index 100% rename from chrome/icons/icon19spring.png rename to icons/icon19spring.png diff --git a/chrome/icons/icon19xmas.png b/icons/icon19xmas.png similarity index 100% rename from chrome/icons/icon19xmas.png rename to icons/icon19xmas.png diff --git a/chrome/icons/icon48.png b/icons/icon48.png similarity index 100% rename from chrome/icons/icon48.png rename to icons/icon48.png diff --git a/manifest.json b/manifest.json index 64285a94..e568f042 100644 --- a/manifest.json +++ b/manifest.json @@ -1,71 +1,32 @@ { "name": "Unblock Youku", - "version": "3.10.0", - "manifest_version": 2, - "minimum_chrome_version": "80.0", + "version": "4.0.0", + "manifest_version": 3, + "minimum_chrome_version": "96.0", + "default_locale": "en", "description": "__MSG_description__", "icons": { - "16": "chrome/icons/icon16.png", - "48": "chrome/icons/icon48.png", - "128": "chrome/icons/icon128.png" + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" }, - "permissions": [ - "proxy", - "webRequest", - "webRequestBlocking", - "storage", - "http://*/*", - "https://*/*" - ], "background": { - "scripts": [ - "shared/urls.js", - "shared/tools.js", - "chrome/pages/js/analytics.js", - "chrome/storage.js", - "chrome/header.js", - "chrome/proxy.js", - "chrome/redirect.js", - "chrome/config.js", - "chrome/donation.js" - ] + "service_worker": "src/background.mjs", + "type": "module" }, - "browser_action": { + "action": { "default_title": "Unblock Youku isn't working properly! Visit http://uku.im/faq for solutions.", - "default_icon": "chrome/icons/icon19gray.png", - "default_popup": "chrome/pages/popup.html" + "default_icon": "icons/icon19gray.png", + "default_popup": "src/popup.html" }, - "options_page": "chrome/pages/options.html", - "content_scripts": [ - { - "matches": ["http://www.tudou.com/*"], - "js": ["chrome/content/tudou.js"], - "run_at": "document_end", - "all_frames": true - }, { - "matches": ["http://play.baidu.com/*"], - "css": ["chrome/content/play.baidu.css"], - "all_frames": true - }, { - "matches": ["http://music.163.com/*"], - "js": ["chrome/content/music.163.js"], - "run_at": "document_end", - "all_frames": true - }, { - "matches": [ - "http://unblockcn.com/*", - "http://*.unblockcn.com/*", - "http://unblockyouku.cn/*", - "http://*.unblockyouku.cn/*", - "http://724sky.com/*", - "http://*.724sky.com/*", - "http://tieba.baidu.com/f?kw=unblockcn*", - "http://*.tieba.baidu.com/f?kw=unblockcn*" - ], - "js": ["chrome/content/unblockcn.js"], - "run_at": "document_end", - "all_frames": true - } + "options_page": "src/options.html", + "permissions": [ + "proxy", + "declarativeNetRequestWithHostAccess", + "storage" ], - "default_locale": "en" + "host_permissions": [ + "http://*/*", + "https://*/*" + ] } diff --git a/package.json b/package.json index 258e8058..25698086 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,15 @@ { - "name": "ub.uku.js", - "version": "0.0.1", - "dependencies": { - "colors": "*", - "nomnom": "1.8.x", - "request": "2.83.x", - "uglify-js": "2.7.x", - "validator": "9.1.x" - }, - "scripts": { - "start": "node server/server.js" - }, + "license": "AGPL-3.0", "engines": { - "node": "8.9.x", - "npm": "5.5.x" + "node": "18.x", + "npm": "8.x" }, - "repository": { - "type": "git", - "url": "https://github.com/zhuzhuor/Unblock-Youku.git" - }, - "license": "AGPL-3.0", + "type": "module", "devDependencies": { - "eslint": "^8.18.0" + "eslint": "^8.18.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.2.3", + "eslint-plugin-promise": "^6.0.0" } } diff --git a/server/server.js b/server/server.js deleted file mode 100755 index 15e1833e..00000000 --- a/server/server.js +++ /dev/null @@ -1,269 +0,0 @@ -#!/usr/bin/env node - -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - -var net = require('net'); -var url = require('url'); -var util = require('util'); -var domain = require('domain'); -var cluster = require('cluster'); -var http = require('http'); -http.globalAgent.maxSockets = Infinity; - -// mikeal/request might have a bug -// always obtain TLS errors when proxying over https -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - -var colors = require('colors'); -var request = require('request'); -var validator = require('validator'); - -var shared_tools = require('../shared/tools'); -var server_utils = require('./utils'); - - -function check_url_format(str) { - if (!validator.isURL(str, { - protocols: ['http', 'https'], - require_tld: true, - require_protocol: true - })) { - return 'Input Error: Invalid URL format'.red; - } -// return; // return nothing -} - -var opts = require("nomnom") - // .help('Run a redirect server for the Chrome extension') - .option('nolog', { - flag: true, - help: 'Do not print request logs' - }) - .option('proxy', { - type: 'string', - metavar: 'HTTP://IP_ADDRESS:PORT', - help: 'Send requests through a remote proxy', - callback: check_url_format - }) - .option('bak_proxy', { - type: 'string', - help: 'Set another proxy server for backup', - metavar: 'HTTP://IP_ADDRESS:PORT', - callback: check_url_format - }) - .option('pac_proxy', { - type: 'string', - help: 'Use a different proxy in the PAC file', - metavar: 'HTTP://IP_ADDRESS:PORT', - callback: check_url_format - }) - .parse(); -// console.log(opts); - -var local_port = process.env.PORT || 8888; -if ((!opts.proxy) && process.env.PROXY_ADDR) { - opts.proxy = process.env.PROXY_ADDR; -} -if ((!opts.bak_proxy) && process.env.BAK_PROXY_ADDR) { - opts.bak_proxy = process.env.BAK_PROXY_ADDR; -} -if (!opts.pac_proxy) { - if (process.env.PAC_PROXY_ADDR) { - opts.pac_proxy = process.env.PAC_PROXY_ADDR; - } else if (opts.proxy) { - opts.pac_proxy = opts.proxy; - } -} - -//opts.proxy = 'http://abc.com:8888'; - -if ((opts.proxy && (check_url_format(opts.proxy) !== undefined)) - || (opts.bak_proxy && (check_url_format(opts.bak_proxy) !== undefined)) - || (opts.pac_proxy && (check_url_format(opts.pac_proxy) !== undefined))) { - console.error('ENV Error: Invalid URL format'.red); - process.exit(400); -} -//console.log(opts); - -var pac_file_content = null; -var pac_proxy_obj = null; -if (opts.pac_proxy) { - pac_proxy_obj = url.parse(opts.pac_proxy); - if (!pac_proxy_obj.host || !pac_proxy_obj.protocol) { - console.error('Invalid URL format parsed'.red); - process.exit(401); - } - pac_file_content = server_utils.generate_pac_file(pac_proxy_obj.host, pac_proxy_obj.protocol); -} else { - pac_file_content = 'function FindProxyForURL(url, host) {return "DIRECT";}'; -} -// console.log(pac_file_content); - - -function http_req_handler(client_request, client_response) { - if (!opts.nolog) { - console.log( - '[ub.uku.js] ' - + client_request.connection.remoteAddress + ': ' - + client_request.method + ' ' + client_request.url.underline); - } - - if ((client_request.url === '/proxy.pac' || client_request.url === '/pac.pac') - || (!shared_tools.string_starts_with(client_request.url, '/proxy'))) { - server_utils.static_responses(client_request, client_response, pac_file_content); - } - - var target = server_utils.get_real_target(client_request.url); - if (!target.host) { - client_response.writeHead(403, { - 'Cache-Control': 'public, max-age=14400' - }); - client_response.end(); - return; - } - - // access control - if (!server_utils.is_valid_url(target.href)) { - client_response.writeHead(403, { - 'Cache-Control': 'public, max-age=14400' - }); - client_response.end(); - return; - } - - - var proxy_request_headers = server_utils.filter_request_headers(client_request.headers); - proxy_request_headers.Host = target.host; - var proxy_request_options = { - encoding: null, // disable auto `buffer.toString()` in package "request" - url: target.href, -// url: 'http://httpbin.org/status/400', - method: client_request.method, - headers: proxy_request_headers - }; - if (opts.proxy) { - proxy_request_options.proxy = opts.proxy; - } - - function handle_proxy_request_error() { - try { - client_response.writeHead(500); - client_response.end('Error occurred, sorry.'); - } catch (er) { - console.error('[ub.uku.js] Error sending 500', er, client_request.url); - } - } - - function handle_proxy_response_data(response, payload) { - var filtered_headers = server_utils.filter_response_headers(response.headers); - client_response.writeHead(response.statusCode, filtered_headers); - client_response.end(payload); - } - - request(proxy_request_options, function(err, resp, body) { - if (err) { - console.error('[ub.uku.js] first proxy_request error: (' + err.code + ') ' + err.message, client_request.url); - if (opts.bak_proxy) { // retry the request - setTimeout(function() { // run it in 1s - proxy_request_options.proxy = opts.bak_proxy; - request(proxy_request_options, function(err, resp, body) { - if (err) { - console.error('[ub.uku.js] second proxy_request error: (' + err.code + ') ' + err.message, client_request.url, err.stack); - handle_proxy_request_error(); - - } else { - handle_proxy_response_data(resp, body); - } - }); - }, 1000); - - } else { - handle_proxy_request_error(); - } - - } else { - handle_proxy_response_data(resp, body); - } - }); -} - - -if (cluster.isMaster) { - var num_CPUs = require('os').cpus().length; - // num_CPUs = 1; - - var i; - for (i = 0; i < num_CPUs; i++) { - cluster.fork(); - // one note here - // the fork() in nodejs is not the same as the fork() in C - // fork() in nodejs will run the whole code from beginning - // not from where it is invoked - } - - cluster.on('listening', function(worker, addr_port) { - // use ub.uku.js as keyword for searching in log files - util.log('[ub.uku.js] Worker ' + worker.process.pid - + ' is now connected to ' + addr_port.address + ':' + addr_port.port); - }); - - cluster.on('exit', function(worker, code, signal) { - if (signal) { - util.log('[ub.uku.js] Worker ' + worker.process.pid - + ' was killed by signal: ' + signal); - } else if (code !== 0) { - console.error('[ub.uku.js] Worker ' + worker.process.pid - + ' exited with error code: ' + code); - // respawn a worker process when one dies - cluster.fork(); - } else { - console.error('[ub.uku.js] Worker ' + worker.process.pid + ' exited.'); - } - }); - - console.log('The redirect server is running...'.green); - if (opts.proxy) { - console.log(('Using the remote proxy: ' + opts.proxy).green); - } else { - console.log('Running locally without remote proxies.'.green); - } - if (opts.bak_proxy) { - console.log(('Using the backup proxy: ' + opts.bak_proxy).green); - } - if (opts.pac_proxy) { - console.log(('The PAC file uses the proxy: ' + opts.pac_proxy).green); - } - -} else if (cluster.isWorker) { - var ubuku_server = http.createServer(); - ubuku_server.on('request', http_req_handler); - - ubuku_server.listen(local_port, '0.0.0.0').on('error', function(err) { - if (err.code === 'EADDRINUSE') { - console.error('[ub.uku.js] Port number is already in use! Exiting now...'); - process.exit(); - } - }); -} - - -process.on('uncaughtException', function(err) { - console.error('[ub.uku.js] Caught uncaughtException: ' + err, err.stack); - process.exit(213); -}); diff --git a/server/utils.js b/server/utils.js deleted file mode 100644 index 5dfb9879..00000000 --- a/server/utils.js +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -'use strict'; - -var url = require('url'); -var util = require('util'); -var http = require('http'); -var querystring = require('querystring'); -var uglify = require('uglify-js'); - -var shared_urls = require('../shared/urls'); -var shared_tools = require('../shared/tools'); - -var string_starts_with = shared_tools.string_starts_with; -var to_title_case = shared_tools.to_title_case; - - -function get_real_target(req_path) { - var real_target = {}; - var real_url = null; - - if (string_starts_with(req_path, '/proxy/http/')) { - real_url = 'http://' + req_path.substring('/proxy/http/'.length); - } else if (string_starts_with(req_path, '/proxy/https/')) { - real_url = 'https://' + req_path.substring('/proxy/https/'.length); - } else { - real_url = querystring.parse(url.parse(req_path).query).url; - if (real_url) { - // to use urlsafe_b64encode - real_url = real_url.replace('-', '+').replace('_', '/'); - - // fix possible padding errors - // real_url += (new Array((4 - real_url.length % 4) % 4 + 1)).join('='); - var i; - for (i = 0; i < (4 - real_url.length % 4) % 4; i++) { - real_url += '='; - } - - var buf = new Buffer(real_url, 'base64'); - real_url = buf.toString(); - } - } - - if (real_url) { - real_target = url.parse(real_url); - if (!real_target.port) { - real_target.port = 80; - } - } - - return real_target; -} - - -function is_valid_url(target_url) { - var i; - for (i = 0; i < shared_urls.regex_crx_bypass_urls.length; i++) { - if (shared_urls.regex_crx_bypass_urls[i].test(target_url)) { - return false; - } - } - for (i = 0; i < shared_urls.regex_crx_urls.length; i++) { - if (shared_urls.regex_crx_urls[i].test(target_url)) { - return true; - } - } - - return false; -} - - -function filter_request_headers(headers) { - var ret_headers = {}; - - var field; - for (field in headers) { - if (headers.hasOwnProperty(field)) { - if (string_starts_with(field, 'proxy-')) { - if (field === 'proxy-connection') { - ret_headers.Connection = headers['proxy-connection']; - } - } else if (field === 'user-agent') { - if (headers['user-agent'].indexOf('CloudFront') !== -1 || - headers['user-agent'].indexOf('CloudFlare') !== -1) { - ret_headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) ' + - 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36'; - } else { - ret_headers['User-Agent'] = headers['user-agent']; - } - } else if (field !== 'cookie' && - field !== 'via' && - (!string_starts_with(field, 'x-'))) { - // in case some servers do not recognize lower-case headers, such as hacker news - ret_headers[to_title_case(field)] = headers[field]; - } - } - } - - return ret_headers; -} - - -function filter_response_headers(headers) { - var res_headers = {}; - - var field; - for (field in headers) { - if (headers.hasOwnProperty(field)) { - if (string_starts_with(field, 'proxy-')) { - if (field === 'proxy-connection') { - res_headers.Connection = headers['proxy-connection']; - } - } else if (field !== 'set-cookie' && - field !== 'cache-control' && - field !== 'expires' && - field !== 'pragma' && - field !== 'age' && - field !== 'via' && - field !== 'server' && - (!string_starts_with(field, 'x-'))) { - res_headers[field] = headers[field]; - } - } - } - res_headers['cache-control'] = 'public, max-age=3600'; - res_headers.server = '; DROP TABLE servertypes; --'; - - return res_headers; -} - - -function static_responses(client_request, client_response, pac_file_content) { - if (client_request.url === '/crossdomain.xml') { - client_response.writeHead(200, { - 'Content-Type': 'text/xml', - 'Content-Length': '113', - 'Cache-Control': 'public, max-age=2592000' - }); - client_response.end('\n' + - ''); - return; - } - - if (client_request.url === '/status') { - var status_text = 'OK'; - - client_response.writeHead(200, { - 'Content-Type': 'text/plain', - 'Content-Length': status_text.length.toString(), - 'Cache-Control': 'public, max-age=3600' - }); - client_response.end(status_text); - return; - } - - if (client_request.url === '/proxy.pac' || client_request.url === '/pac.pac') { - var content_type = 'application/x-ns-proxy-autoconfig'; - if (client_request.headers['user-agent'] !== undefined && - client_request.headers['user-agent'].indexOf('PhantomJS') !== -1) { - content_type = 'text/plain'; - } - client_response.writeHead(200, { - 'Content-Type': content_type, - 'Content-Length': pac_file_content.length.toString(), - 'Cache-Control': 'public, max-age=14400' - }); - client_response.end(pac_file_content); - return; - } - - if (client_request.url === '/favicon.ico') { - client_response.writeHead(404, { - 'Cache-Control': 'public, max-age=2592000' - }); - client_response.end(); - return; - } - - if (client_request.url === '/robots.txt') { - client_response.writeHead(200, { - 'Content-Type': 'text/plain', - 'Content-Length': '25', - 'Cache-Control': 'public, max-age=2592000' - }); - client_response.end('User-agent: *\nDisallow: /'); - return; - } - - if (client_request.url === '/regex') { - var regex_list = shared_urls.produce_squid_regex_list(true /* for PAC proxy */); - var regex_text = regex_list.join('\n'); - - client_response.writeHead(200, { - 'Content-Type': 'text/plain', - 'Content-Length': regex_text.length.toString(), - 'Cache-Control': 'public, max-age=3600' - }); - client_response.end(regex_text); - return; - } - - if (client_request.url === '/chrome_regex') { - var chrome_regex_list = shared_urls.produce_squid_regex_list(false /* for Chrome proxy */); - var chrome_regex_text = chrome_regex_list.join('\n'); - - client_response.writeHead(200, { - 'Content-Type': 'text/plain', - 'Content-Length': chrome_regex_text.length.toString(), - 'Cache-Control': 'public, max-age=60' - }); - client_response.end(chrome_regex_text); - return; - } - - client_response.writeHead(403, { - 'Cache-Control': 'public, max-age=14400' - }); - client_response.end(); -} - - -function generate_pac_file(proxy_addr_port, proxy_protocol) { - return '/*\n' + - ' Installing/using this software, you agree that this software is\n' + - ' only for study purposes and its authors and service providers \n' + - ' take no responsibilities for any consequences.\n' + - '*/\n' + - uglify.minify( - shared_tools.urls2pac( - shared_urls.pac_bypass_urls, - shared_urls.pac_urls, - proxy_addr_port, - proxy_protocol - ), - {fromString: true} - ).code; -} - - -exports.get_real_target = get_real_target; -exports.is_valid_url = is_valid_url; -exports.filter_request_headers = filter_request_headers; -exports.filter_response_headers = filter_response_headers; -exports.static_responses = static_responses; -exports.generate_pac_file = generate_pac_file; diff --git a/shared/produce_regex.js b/shared/produce_regex.js deleted file mode 100644 index 4fcb9fc3..00000000 --- a/shared/produce_regex.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const shared_urls = require('../shared/urls'); - -const chrome_regex_list = shared_urls.produce_squid_regex_list(/* for_pac_server= */ false); -const chrome_regex_text = chrome_regex_list.join('\n') + '\n'; -console.log(chrome_regex_text); - -process.exit(0); \ No newline at end of file diff --git a/shared/tools.js b/shared/tools.js deleted file mode 100644 index 1463736a..00000000 --- a/shared/tools.js +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - -function new_random_ip() { - "use strict"; - var ip_addr = '220.181.111.'; - //ip_addr += Math.floor(Math.random() * 255) + '.'; - ip_addr += Math.floor(Math.random() * 254 + 1); // 1 ~ 254 - return ip_addr; -} - - -function string_starts_with(str, substr) { - "use strict"; - return str.slice(0, substr.length) === substr; -} - - -function _parse_url(url_str) { - "use strict"; - var protocol = null; - if (string_starts_with(url_str, 'http://')) { - url_str = url_str.slice('http://'.length); - protocol = 'http'; - } else if (string_starts_with(url_str, 'https://')) { - url_str = url_str.slice('https://'.length); - protocol = 'https'; - } else { - console.error('URL does not start with http:// or https://'); - return null; - } - - var path_idx = url_str.indexOf('/'); - if (path_idx < 0) { - path_idx = url_str.length; - url_str += '/'; - } - var colon_idx = url_str.indexOf(':'); // the colon before the optional port number - - var sep_idx = path_idx; - if (colon_idx >= 0 && colon_idx < path_idx) { - sep_idx = colon_idx; - } - - return { - protocol: protocol, - // the parameter in FindProxyForURL only doesn't contain port numbers - hostname: url_str.slice(0, sep_idx), - portpath: url_str.slice(sep_idx) - }; -} -// console.log(_parse_url('http://test.com')); -// console.log(_parse_url('http://test.com:123')); -// console.log(_parse_url('http://test.com/path')); -// console.log(_parse_url('http://test.com:123/path')); - - -function gen_url_map(protocol, white_ulist, proxy_ulist) { - "use strict"; - var url_map = { - white: { - any: [] - }, - proxy: { - any: [] - } - }; - - function stringify(map_obj) { - var white_map = map_obj.white; - var proxy_map = map_obj.proxy; - - var res_str = [ - "{", - " 'white': {" - ].join("\n") + "\n"; - res_str += stringify_patterns(white_map); - - res_str += [ - " },", - " 'proxy': {" - ].join("\n") + "\n"; - res_str += stringify_patterns(proxy_map); - - res_str += [ - " }", - "}" - ].join("\n"); - - return res_str; - } - - function stringify_patterns(hostname_map) { - var res_str = ""; - var i, patterns = null; - - for (var hostname in hostname_map) { - if (hostname_map.hasOwnProperty(hostname)) { - res_str += " '" + hostname + "': ["; - patterns = hostname_map[hostname]; - - if (patterns.length === 0) { - res_str += "],\n"; - - } else { - res_str += "\n"; - for (i = 0; i < patterns.length; i++) { - res_str += " " + patterns[i] + ",\n"; - } - res_str = res_str.slice(0, -2) + '\n'; // remove the last , - res_str += " ],\n"; - } - } - } - res_str = res_str.slice(0, -2) + '\n'; // remove the last , - - return res_str; - } - - function add_patterns(map_obj, ulist) { - var i, uobj, hostname, portpath; - var key, val; - for (i = 0; i < ulist.length; i++) { - uobj = _parse_url(ulist[i]); - if (uobj === null) { - console.error('Invalid URL pattern: ' + ulist[i]); - continue; - } - - if (uobj.protocol === protocol) { - hostname = uobj.hostname; - portpath = uobj.portpath; - if (hostname.indexOf('*') >= 0) { - if (hostname.slice(1).indexOf('*') >= 0) { // * is only allowed to be the first char - console.error('Invalid wildcard URL pattern: ' + ulist[i]); - continue; - - } else { - key = 'any'; - val = hostname + portpath; // host:port/path - } - - } else { - if (!map_obj.hasOwnProperty(hostname)) { - map_obj[hostname] = []; - } - key = hostname; - val = portpath; // only :port/path - } - - val = val.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&'); - val = val.replace(/\*/g, '.*'); - val = val.replace(/^\.\*/i, '[^\/]*'); // if starts with *; should not be possible for :port or /path - // val = new RegExp('^' + val + '$', 'i'); - val = '/^' + val + '$/i'; - - if (val.slice(-5) === '.*$/i') { - val = val.slice(0, -5) + '/i'; - } - - map_obj[key].push(val); - } // if - } // for - } - - - add_patterns(url_map.white, white_ulist); - add_patterns(url_map.proxy, proxy_ulist); - - // console.log(stringify(url_map)); - return stringify(url_map); -} - - -function urls2pac(url_whitelist, url_list, - proxy_server_1, proxy_protocol_1, - proxy_server_2, proxy_protocol_2) { - "use strict"; - var http_map_str = gen_url_map('http', url_whitelist, url_list); - var https_map_str = gen_url_map('https', url_whitelist, url_list); - - if (typeof proxy_protocol_1 === 'undefined') { - proxy_protocol_1 = 'PROXY'; - } else { - proxy_protocol_1 = proxy_protocol_1.replace(/:/g,''); - proxy_protocol_1 = proxy_protocol_1.replace(/\//g,''); - proxy_protocol_1 = proxy_protocol_1.toUpperCase(); - if (proxy_protocol_1 === 'HTTP') { - proxy_protocol_1 = 'PROXY'; - } - } - - var _proxy_str = proxy_protocol_1 + " " + proxy_server_1 + "; "; - - if (typeof proxy_server_2 !== 'undefined') { - if (typeof proxy_protocol_2 === 'undefined') { - proxy_protocol_2 = 'PROXY'; - } - proxy_protocol_2 = proxy_protocol_2.replace(/:/g,''); - proxy_protocol_2 = proxy_protocol_2.replace(/\//g,''); - proxy_protocol_2 = proxy_protocol_2.toUpperCase(); - if (proxy_protocol_2 === 'HTTP') { - proxy_protocol_2 = 'PROXY'; - } - - _proxy_str += proxy_protocol_2 + " " + proxy_server_2 + "; "; - } - - _proxy_str += "DIRECT;"; - - return [ - "var _http_map = " + http_map_str + ";", - "var _https_map = " + https_map_str + ";", - "var _proxy_str = '" + _proxy_str + "';", - "", - "function _check_regex_list(regex_list, str) {", - " if (str.slice(0, 4) === ':80/')", - " str = str.slice(3);", - " for (var i = 0; i < regex_list.length; i++)", - " if (regex_list[i].test(str))", - " return true;", - " return false;", - "}", - "", - "function _check_patterns(patterns, hostname, full_url, prot_len) {", - " if (patterns.hasOwnProperty(hostname))", - " if (_check_regex_list(patterns[hostname],", - " full_url.slice(prot_len + hostname.length)))", // check only :port/path - " return true;", - " if (_check_regex_list(patterns.any,", // try our best to speed up the checking for non-proxied urls - " full_url.slice(prot_len)))", // check hostname:port/path - " return true;", - " return false;", - "}", - "", - "function _find_proxy(url_map, host, url, prot_len) {", - " if (_check_patterns(url_map.white, host, url, prot_len))", - " return 'DIRECT';", - " if (_check_patterns(url_map.proxy, host, url, prot_len))", - " return _proxy_str;", - " return 'DIRECT';", - "}", - "", - "function FindProxyForURL(url, host) {", // host doesn't contain port - " var prot = url.slice(0, 6);", - " if (prot === 'http:/')", - " return _find_proxy(_http_map, host, url, 7);", // 'http://'.length - " else if (prot === 'https:')", - " return _find_proxy(_https_map, host, url, 8);", // 'https://'.length - " return 'DIRECT';", - "}" - ].join("\n") + "\n"; -} - - -// change host to Host, or user-agent to User-Agent -function to_title_case(str) { - "use strict"; - // just a little differnt from http://goo.gl/IGhfR - return str.replace(/\w[^\-\s]*/g, function(txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - }); -} - - -var exports = exports || {}; -exports.new_random_ip = new_random_ip; -exports.urls2pac = urls2pac; -exports.string_starts_with = string_starts_with; -exports.to_title_case = to_title_case; - - -(function() { - "use strict"; - - if (typeof module !== 'undefined' && module.exports && require.main === module) { - var shared_urls = require('./urls'); - var pac_content = urls2pac(shared_urls.pac_bypass_urls, shared_urls.pac_urls, 'localhost:8888'); - console.log(pac_content); - } -}()); diff --git a/shared/urls.js b/shared/urls.js deleted file mode 100644 index 3a17eac0..00000000 --- a/shared/urls.js +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Copyright (C) 2012 - 2016 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -var unblock_youku = unblock_youku || {}; - -/* - * Note that the HTTPS URLs must NOT have any path portions. - * - * For example: - * - Wrong: https://example.com/abc/* - * - Right: https://example.com/* - */ - - -// The HTTP headers of these URLs will be modified. -unblock_youku.header_urls = [ - 'http://*.ssports.com/*', - 'http://ssports.com/*', - 'http://ssports.smgbb.cn/*', - 'https://pstream.api.mgtv.com/*', - 'http://kandian.com/player/getEpgInfo*', -]; - -// These URLs will go through a proxy. -unblock_youku.chrome_proxy_urls = [ - 'https://dmd-fifajs-h5-ikuweb.youku.com/*', - 'https://dmd-fifa-h5-ikuweb.youku.com/*', - 'http://acs.youku.com/*', - 'https://acs.youku.com/*', - // 'http://v.youku.com/player/*', - 'http://pl-ali.youku.com/*', - 'http://list.youku.com/category/*', - 'http://api.youku.com/player/*', - 'http://play.youku.com/play/get.json*', - 'http://play-dxk.youku.com/play/get.json*', - 'http://play-ali.youku.com/play/get.json*', - 'http://list.youku.com/show/module?*', - 'http://list.youku.com/show/point?*', - 'http://list.youku.com/show/episode?*', - 'http://v.youku.com/page/playlist*', - 'http://ups.youku.com/ups/get.json*', - 'https://ups.youku.com/', - 'https://ac.qq.com/*', - 'http://player.aplus.pptv.com/*', - 'http://api.aixifan.com/plays/*', - 'http://www.acfun.cn/video/getVideo*', - 'https://www.acfun.cn/*', - // 'https://v.youku.com/*', - // 'http://v2.tudou.com/*', - 'http://video.tudou.com/v/*', - 'http://www.tudou.com/a/*', - 'http://www.tudou.com/v/*', - 'http://www.tudou.com/outplay/goto/*', - 'http://www.tudou.com/tvp/alist.action*', - 'http://s.plcloud.music.qq.com/fcgi-bin/p.fcg*', - 'http://i.y.qq.com/*/fcg-bin/*', - 'http://i.y.qq.com/*/fcgi-bin/*', - 'http://c.y.qq.com/*/fcg-bin/*', - 'http://c.y.qq.com/*/fcgi-bin/*', - 'https://c.y.qq.com/*', - 'http://api.unipay.qq.com/cgi-bin/get_pay_info.fcg?*', - 'https://api.unipay.qq.com/*', - 'http://hot.vrs.sohu.com/*', - 'https://hot.vrs.sohu.com/*', - 'http://live.tv.sohu.com/live/player*', - 'http://pad.tv.sohu.com/playinfo*', - 'http://my.tv.sohu.com/play/m3u8version.do*', - 'http://hot.vrs.letv.com/*', - 'http://api.le.com/mms/out/video/*', - 'http://player.pc.le.com/mms/out/video/*', - 'http://player-pc.le.com/mms/out/video/*', - // 'http://g3.letv.cn/*', - 'http://data.video.qiyi.com/*', - 'http://data.video.iqiyi.com/*', - 'https://data.video.qiyi.com/*', - 'https://data.video.iqiyi.com/*', - // 'http://nl.rcd.iqiyi.com/apis/urc/*', - 'http://cache.vip.qiyi.com/*', - 'http://cache.video.qiyi.com/*', - 'http://cache.vip.iqiyi.com/*', - 'http://cache.video.iqiyi.com/*', - 'https://cache.video.iqiyi.com/*', - 'http://iplocation.geo.qiyi.com/cityjson*', - 'http://iplocation.geo.iqiyi.com/cityjson*', - 'http://*.cupid.iqiyi.com/*', - 'http://v.api.hunantv.com/player/video*', - 'http://mobile.api.hunantv.com/v5/video/getSource*', - 'http://v.api.mgtv.com/player/video*', - 'https://v.api.mgtv.com/*', - 'http://pcweb.api.mgtv.com/player/video*', - 'https://pcweb.api.mgtv.com/*', - 'http://acc.music.qq.com/base/fcgi-bin/getsession*', - 'http://182.254.116.117/*', - - 'http://api.appsdk.soku.com/*', - - 'http://app.bilibili.com/bangumi/*', - 'http://bangumi.bilibili.com/*', - - 'http://122.72.82.31/*', - 'http://211.151.158.155/*', - - // 'http://tt.video.qq.com/get*', - // 'http://ice.video.qq.com/get*', - // 'http://tjsa.video.qq.com/get*', - // 'http://a10.video.qq.com/get*', - // 'http://xyy.video.qq.com/get*', - // 'http://vcq.video.qq.com/get*', - // 'http://vsh.video.qq.com/get*', - // 'http://vbj.video.qq.com/get*', - // 'http://bobo.video.qq.com/get*', - // 'http://flvs.video.qq.com/get*', - // 'http://bkvv.video.qq.com/get*', - // 'http://h5vv.video.qq.com/get*', - 'http://*.video.qq.com/get*', - 'http://*.video.qq.com/fcgi-bin/*', - 'https://*.video.qq.com/*', - 'http://info.zb.qq.com/?*', - 'https://info.zb.qq.com/*', - 'http://info.zb.video.qq.com/?*', - 'https://info.zb.video.qq.com/*', - 'http://qzs.qq.com/tencentvideo_v1/*', - 'https://qzs.qq.com/*', - 'http://vd.l.qq.com/*', - 'https://vd.l.qq.com/*', - 'http://vi.l.qq.com/*', - 'https://vi.l.qq.com/*', - - 'http://dispatcher.video.sina.com.cn/*', - 'http://geo.js.kankan.com/*', - 'http://web-play.pptv.com/*', - 'http://v.pptv.com/show/*', - 'https://ppi.api.pptv.com/*', - 'http://web-play.pplive.cn/*', - 'http://tools.aplusapi.pptv.com/get_ppi?*', - 'http://live.pptv.com/api/subject_list?*', - // 'http://c1.pptv.com/*', - 'http://dyn.ugc.pps.tv/*', - 'http://v.pps.tv/ugc/ajax/aj_html5_url.php*', - 'http://inner.kandian.com/*', - 'http://ipservice.163.com/*', - 'http://so.open.163.com/open/info.htm*', - 'http://zb.s.qq.com/*', - 'https://zb.s.qq.com/*', - 'http://ip.kankan.com/*', - 'http://vxml.56.com/json/*', - - 'http://music.sina.com.cn/yueku/intro/*', - //'http://ting.baidu.com/data/music/songlink*', - //'http://ting.baidu.com/data/music/songinfo*', - //'http://ting.baidu.com/song/*/download*', - 'http://music.sina.com.cn/radio/port/webFeatureRadioLimitList.php*', - 'http://play.baidu.com/data/music/songlink*', - - 'http://v.iask.com/v_play.php*', - 'http://v.iask.com/v_play_ipad.cx.php*', - 'http://tv.weibo.com/player/*', - 'http://wtv.v.iask.com/*.m3u8*', - 'http://wtv.v.iask.com/mcdn.php', - 'http://video.sina.com.cn/interface/l/u/getFocusStatus.php*', - 'http://wtv.v.iask.com/player/ovs1_idc_list.php*', - - //'http://kandian.com/player/getEpgInfo*', // !!! - //'http://cdn.kandian.com/*', - 'http://www.yinyuetai.com/insite/*', - 'http://www.yinyuetai.com/main/get-*', - 'http://www.kugou.com/interface/geoip/*', - 'http://www.kuwo.cn/yy/PlayCheckIp?callback=checkIpCallback&_=*', - 'http://antiserver.kuwo.cn/anti.s?*', - 'http://ipcheck.kuwo.cn/ip_check.kuwo*', - 'http://*.dpool.sina.com.cn/iplookup*', - 'http://api.letv.com/streamblock*', - 'http://api.letv.com/mms/out/video/play*', - 'http://api.www.letv.com/mms/out/video/playJson?*', - 'http://*.letv.com/mms/out/video/play*', - 'http://api.letv.com/mms/out/common/geturl*', - 'http://api.letv.com/geturl*', - 'http://api.letv.com/api/geturl*', - 'http://st.live.letv.com/live/*', - 'http://live.gslb.letv.com/gslb?*', - 'http://live.g3proxy.lecloud.com/gslb?*', - 'http://api.live.letv.com/crossdomain.xml', - 'http://static.itv.letv.com/api*', - 'http://ip.apps.cntv.cn/js/player.do*', - 'http://vdn.apps.cntv.cn/api/get*', - 'http://vdn.live.cntv.cn/api2/*', - 'http://cctv1.vtime.cntv.cloudcdn.net/cache/*', - 'http://cctv5.vtime.cntv.cloudcdn.net/cache/*', - 'http://cctv5plus.vtime.cntv.cloudcdn.net/cache/*', - 'http://cctv13.vtime.cntv.cloudcdn.net/cache/*', - 'http://sports1pull.live.wscdns.com/live/aoyun2', - 'http://vip.sports.cntv.cn/check.do*', - 'http://vip.sports.cntv.cn/play.do*', - 'http://vip.sports.cntv.cn/servlets/encryptvideopath.do*', - 'http://211.151.157.15/*', - - 'http://www.tudou.com/programs/view/*', - 'http://www.tudou.com/albumplay/*', - 'http://www.tudou.com/listplay/*', - - 'http://www.youku.com/show_page/*', - 'http://v.youku.com/v_show/*', - 'http://www.soku.com/search_video/*', - 'http://search.api.3g.youku.com/*', - 'http://search.api.3g.tudou.com/*', - "http://*.api.tv.itc.cn/*", - "http://api.tv.sohu.com/*", - 'http://ac.qq.com/Comic*', - 'http://ac.qq.com/Jump*', - "http://live.api.hunantv.com/pc/getSourceById*", - "http://mobile.api.hunantv.com/*", - 'http://www.qie.tv/*', - 'http://www.bilibili.com/video/*', - 'https://www.bilibili.com/*', - 'http://api.bilibili.com/*', - 'https://api.bilibili.com/*', - 'http://interface.bilibili.com/*', - 'https://interface.bilibili.com/*', - 'http://bangumi.bilibili.com/api/*', - 'https://bangumi.bilibili.com/*', - // 'http://live-play.acgvideo.com/live/*', - 'http://m10.music.126.net/*', //for the testing of netease music - - 'http://douban.fm/*', - 'https://douban.fm/*', - 'http://www.xiami.com/*', - 'http://lixian.xunlei.com/*', - 'http://lixian.vip.xunlei.com/*', - 'http://dynamic.cloud.vip.xunlei.com/*', - 'http://cloud.vip.xunlei.com/*', - // 'http://vod.lixian.xunlei.com/*', - 'http://www.iqiyi.com/dongman/', - 'https://www.iqiyi.com/', - - // LETV https://github.com/Unblocker/Unblock-Youku/issues/590 - "http://36.110.222.105/*", - "http://36.110.222.119/*", - "http://36.110.222.146/*", - "http://36.110.222.156/*", - "http://123.125.89.6/*", - "http://123.125.89.101/*", - "http://123.125.89.102/*", - "http://123.125.89.103/*", - "http://123.125.89.157/*", - "http://123.125.89.159/*", - "http://123.126.32.134/*", - "http://123.59.122.75/*", - "http://123.59.122.76/*", - "http://123.59.122.77/*", - "http://123.59.122.104/*", - "http://111.206.208.36/*", - "http://111.206.208.37/*", - "http://111.206.208.38/*", - "http://111.206.208.61/*", - "http://111.206.208.62/*", - "http://111.206.208.163/*", - "http://111.206.208.164/*", - "http://111.206.208.166/*", - "http://111.206.211.145/*", - "http://111.206.211.146/*", - "http://111.206.211.147/*", - "http://111.206.211.148/*", - "http://111.206.211.129/*", - "http://111.206.211.130/*", - "http://111.206.211.131/*", - "http://220.181.153.113/*", - "http://14.152.77.32/*", - "http://14.152.77.26/*", - "http://14.152.77.25/*", - "http://14.152.77.22/*", - "http://183.232.229.22/*", - "http://183.232.229.21/*", - "http://183.232.229.25/*", - "http://183.232.229.32/*", - "http://115.182.200.51/*", - "http://115.182.200.50/*", - "http://115.182.200.54/*", - "http://115.182.200.53/*", - "http://115.182.200.52/*", - "http://115.182.63.51/*", - "http://115.182.63.93/*", - "http://*.letv.cn/vod/v2/*", - "http://ark.letv.com/s*", - "http://search.lekan.letv.com/*", - - // 'http://live.video.sina.com.cn/room/*', - // 'http://edge.v.iask.com/*', // may be large files - - 'http://pay.youku.com/buy/redirect.html*', - 'http://pay.tudou.com/buy/redirect.html*', - 'http://aid.video.qq.com/fcgi-bin/userip?*', - 'http://aidbak.video.qq.com/fcgi-bin/userip?*', - 'http://pay.video.qq.com/fcgi-bin/paylimit*', - 'http://paybak.video.qq.com/fcgi-bin/paylimit*', - 'https://*.l.qq.com/*', - 'http://chrome.2345.com/dianhua/index.php?m=call&f=check&*', - - 'http://music.163.com/eapi/*', - - // 'http://play.baidu.com/*', - // 'http://zhangmenshiting.baidu.com/*', - // 'http://music.baidu.com/box*', - // 'http://music.baidu.com/data/service/sum*', - // 'http://music.baidu.com/data/music/songlink*', - // 'http://music.baidu.com/data/music/songinfo*', - // 'http://music.baidu.com/data/music/fmlink*', - // 'http://music.baidu.com/song/*/download*', - // 'http://fm.baidu.com/*', - // 'http://www.kugou.com/*', - // 'http://music.baidu.com/data/user/collect*', - - // 'http://d.dxy.cn/*', - // 'http://ac.qq.com/*/v/cid/*', - // 'http://v.pptv.com/show/*.html', - // 'http://www.songtaste.com/*', - // 'http://songtaste.com/*', - // 'http://www.yyets.com/*', - // 'http://mainv2.img.duomi.com/*', - // 'http://imanhua.com/comic/*', - // 'http://www.imanhua.com/comic/*', - // 'http://imanhua.com/v2*', - // 'http://www.imanhua.com/v2*', - - // for development purposes - 'http://flask-test-iwauxcyxjb.cn-hangzhou.fcapp.run/*', - 'https://flask-test-iwauxcyxjb.cn-hangzhou.fcapp.run/*' -]; - - -// These URLs will not go through proxy servers (for our Chrome extension). -unblock_youku.chrome_proxy_bypass_urls = [ - 'http://bangumi.bilibili.com/index/ding-count.json', -]; - - -// These URLs are for other software, such as iOS/Android Apps and TV boxes. -unblock_youku.pac_proxy_urls = unblock_youku.chrome_proxy_urls.concat(unblock_youku.header_urls, [ - 'http://a.play.api.3g.youku.com/common/v3/play?*', - 'http://i.play.api.3g.youku.com/common/v3/play?*', - 'http://i.play.api.3g.youku.com/common/v3/hasadv/play?*', - 'http://api.3g.youku.com/layout*', - 'http://api.3g.youku.com/v3/play/address*', - 'http://api.3g.youku.com/openapi-wireless/videos/*/download*', - 'http://api.3g.youku.com/videos/*/download*', - 'http://api.3g.youku.com/common/v3/play*', - 'http://tv.api.3g.youku.com/openapi-wireless/v3/play/address*', - 'http://tv.api.3g.youku.com/common/v3/hasadv/play*', - 'http://tv.api.3g.youku.com/common/v3/play*', - 'http://play.api.3g.youku.com/common/v3/hasadv/play*', - 'http://play.api.3g.youku.com/common/v3/play*', - 'http://play.api.3g.youku.com/v3/play/address*', - 'http://i-play.mobile.youku.com/*', - 'http://play.api.3g.tudou.com/v*', - 'http://tv.api.3g.tudou.com/tv/play?*', - 'http://api.3g.tudou.com/*', - 'http://api.tv.sohu.com/mobile_user/device/clientconf.json?*', - 'http://access.tv.sohu.com/*', - 'http://iface.iqiyi.com/api/searchIface?*', - 'http://iface.iqiyi.com/api/ip2area?*', - 'http://iface2.iqiyi.com/php/xyz/iface/*', - 'http://iface2.iqiyi.com/php/xyz/entry/galaxy.php?*', - 'http://iface2.iqiyi.com/php/xyz/entry/nebula.php?*', - 'http://cache.m.iqiyi.com/jp/tmts/*', - 'http://dynamic.app.m.letv.com/*/dynamic.php?*ctl=videofile*', - 'http://dynamic.meizi.app.m.letv.com/*/dynamic.php?*ctl=videofile*', - 'http://dynamic.search.app.m.letv.com/*/dynamic.php?*ctl=videofile*', - 'http://dynamic.live.app.m.letv.com/*/dynamic.php?*act=canplay*', - 'http://listso.m.areainfo.ppstream.com/ip/q.php*', - 'http://epg.api.pptv.com/detail.api?*', - 'http://play.api.pptv.com/boxplay.api?*', - 'http://api.letv.com/getipgeo', - 'http://m.letv.com/api/geturl?*', - 'http://api.mob.app.letv.com/play*', - 'http://static.api.sports.letv.com/*vod?*', - 'http://api.itv.letv.com/iptv/api/new/video/play/get.json?*', //for letv TV boxes - 'http://vdn.apps.cntv.cn/api/getLiveUrlCommonApi.do?pa://cctv_p2p_hdcctv5*', - 'http://vdn.apps.cntv.cn/api/getLiveUrlCommonApi.do?pa://cctv_p2p_hdcctv6*', - 'http://vdn.apps.cntv.cn/api/getLiveUrlCommonApi.do?pa://cctv_p2p_hdcctv8*', - 'http://vdn.apps.cntv.cn/api/getLiveUrlCommonApi.do?pa://cctv_p2p_hdbtv6*', - // 'http://vdn.live.cntv.cn/api2/live.do?channel=pa://cctv_p2p_hdcctv5*', - // 'http://vdn.live.cntv.cn/api2/live.do?channel=pa://cctv_p2p_hdcctv6*', - // 'http://vdn.live.cntv.cn/api2/live.do?channel=pa://cctv_p2p_hdcctv8*', - // 'http://vdn.live.cntv.cn/api2/live.do?channel=pa://cctv_p2p_hdbtv6*', - 'http://vdn.live.cntv.cn/*', - 'http://app.bilibili.com/*', - 'https://app.bilibili.com/*', - 'http://bangumi.bilibili.com/api/*', - - - // Music apps - 'http://3g.music.qq.com/*', - 'http://mqqplayer.3g.qq.com/*', - 'http://proxy.music.qq.com/*', - 'http://proxymc.qq.com/*', - //Disable follow url because its hijackable. - // 'http://*/base/fcgi-bin/getsession*', //for ios qq music v5.8, issue #536 - 'http://220.249.243.70/base/fcgi-bin/getsession*', - 'http://117.185.116.152/base/fcgi-bin/getsession*', - 'http://101.227.139.217/base/fcgi-bin/getsession*', - 'http://59.37.96.220/base/fcgi-bin/getsession*', - 'http://140.207.69.99/base/fcgi-bin/getsession*', - 'http://103.7.31.186/base/fcgi-bin/getsession*', - 'http://103.7.30.89/base/fcgi-bin/getsession*', - 'http://182.254.34.151/base/fcgi-bin/getsession*', //temperary solutions for issue #536 - 'http://223.167.82.139/base/fcgi-bin/getsession*', - 'http://101.227.169.200/base/fcgi-bin/getsession*', - 'http://182.254.11.174/base/fcgi-bin/getsession*', - 'http://183.192.192.139/base/fcgi-bin/getsession*', - 'http://163.177.90.61/base/fcgi-bin/getsession*', - 'http://14.18.245.250/base/fcgi-bin/getsession*', - 'http://183.232.126.23/base/fcgi-bin/getsession*', - 'http://183.232.119.198/base/fcgi-bin/getsession*', - 'http://182.254.4.234/base/fcgi-bin/getsession*', //another fix for QQ music in #731 - 'http://203.205.151.23/base/fcgi-bin/getsession*', - 'http://ip2.kugou.com/check/isCn/*', - 'http://ip.kugou.com/check/iscn*', - 'http://client.api.ttpod.com/global*', - 'http://mobi.kuwo.cn/*', - 'http://nmobi.kuwo.cn/*', - 'http://mobilefeedback.kugou.com/*', - 'http://tingapi.ting.baidu.com/v1/restserver/ting?*method=baidu.ting.song*', - 'http://music.baidu.com/data/music/links?*', - 'http://serviceinfo.sdk.duomi.com/api/serviceinfo/getserverlist*', - 'http://music.163.com/api/copyright/restrict/?*', - 'http://music.163.com/api/batch', - 'http://www.xiami.com/web/spark*', - 'http://www.xiami.com/web/*?*xiamitoken=*', - 'http://spark.api.xiami.com/api?*method=AuthIp*', - 'http://spark.api.xiami.com/api?*method=Start.init*', - 'http://spark.api.xiami.com/api?*method=Songs.getTrackDetail*', - 'http://spark.api.xiami.com/api?*method=Songs.detail*', - // for PC Clients only - 'http://iplocation.geo.qiyi.com/cityjson', - 'http://sns.video.qq.com/tunnel/fcgi-bin/tunnel*', - 'http://v5.pc.duomi.com/single-ajaxsingle-isban*', - - // the access control for https are per domain name - 'https://openapi.youku.com/*', // see issue #118 - 'https://61.135.196.99/*', //n-openapi.youku.com - 'https://220.181.185.150/*', //zw-openapi.youku.com - 'https://111.13.127.46/*',//bj-m-openapi.youku.com - 'https://211.151.50.10/*',//b-openapi.youku.com - 'https://123.126.99.57/*',//openapi.youku.com - 'https://123.126.99.39/*',//zw-n-openapi.youku.com - 'https://220.181.154.137/*',//zw-t-openapi.youku.com - - // for MiBox iCNTV Authentication - 'http://tms.is.ysten.com:8080/yst-tms/login.action?*', - 'http://chrome.2345.com/dianhua/mobileApi/check.php', - 'http://internal.check.duokanbox.com/check.json*', - // for 3rd party's DNS for Apple TV (see pull request #78) - 'http://180.153.225.136/*', - 'http://118.244.244.124/*', - 'http://210.129.145.150/*', - 'http://182.16.230.98/*', // Updated on Jan. 3, for new DNS of apple tv. - - // for NeteaseMusic on iOS - 'http://103.65.41.126/eapi/*', - 'http://103.65.41.125/eapi/*' -]); - - -// These URLs will not go through proxy servers (for our PAC service). -unblock_youku.pac_proxy_bypass_urls = unblock_youku.chrome_proxy_bypass_urls.concat([ - // 'http://*/ipad?file=/*' // Not useful anymore for iOS -]); - - -(function() { - // Remove all path info from unblock_youku.pac_proxy_urls due to the latest iOS update - // See https://bbs.uku.im/t/43 - "use strict"; - - var new_list = []; - var regex_to_extract_domain = /^https?:\/\/[^\/]*/i; - - for (var i = 0; i < unblock_youku.pac_proxy_urls.length; i++) { - new_list.push(unblock_youku.pac_proxy_urls[i].match(regex_to_extract_domain)[0] + '/*'); - } - - unblock_youku.pac_proxy_urls = new_list; -}()); - - -function urls2regexs(url_list) { - "use strict"; - - var regex_list = []; - - for (var i = 0; i < url_list.length; i++) { - var re_str = url_list[i]; - - // Escape all possibly problematic symbols - // http://stackoverflow.com/a/6969486/1766096 - re_str = re_str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&'); - re_str = re_str.replace(/\*/g, '.*'); - - // make the first * matches only domain names or ip addresses - // just as http://developer.chrome.com/extensions/match_patterns.html - re_str = re_str.replace(/^http:\\\/\\\/\.\*/i, 'http:\\/\\/[^\/]*'); - re_str = re_str.replace(/^https:\\\/\\\/\.\*/i, 'https:\\/\\/[^\/]*'); - - regex_list.push(new RegExp('^' + re_str + '$', 'i')); - } - - // console.log(regex_list); - return regex_list; -} - - -function produce_squid_regex_list(for_pac_server) { - 'use strict'; - - var squid_url_list = unblock_youku.chrome_proxy_urls; - if (for_pac_server === true) { - squid_url_list = unblock_youku.pac_proxy_urls; - } - - var squid_regex_list = urls2regexs(squid_url_list); - var i, single_str; - - var regex_to_extract_https_domain = /^\^https:\\\/\\\/([^:]+)\\\//i; - - var ret_list = []; - for (i = 0; i < squid_regex_list.length; i++) { - single_str = squid_regex_list[i].toString(); - single_str = single_str.substring(1, single_str.length - 2); - - if (single_str.match(regex_to_extract_https_domain)) { - single_str = '^' + single_str.match(regex_to_extract_https_domain)[1] + ':443'; - } - - ret_list.push(single_str); - } - - return ret_list; -} - - -// Also export as a node.js module -var exports = exports || {}; -// Exported for the redirect server -exports.regex_crx_urls = urls2regexs(unblock_youku.chrome_proxy_urls); -exports.regex_crx_bypass_urls = urls2regexs(unblock_youku.chrome_proxy_bypass_urls); -// Exported for generating the PAC file -exports.pac_urls = unblock_youku.pac_proxy_urls; -exports.pac_bypass_urls = unblock_youku.pac_proxy_bypass_urls; - -exports.produce_squid_regex_list = produce_squid_regex_list; diff --git a/src/background.mjs b/src/background.mjs new file mode 100644 index 00000000..cd3a463d --- /dev/null +++ b/src/background.mjs @@ -0,0 +1,17 @@ +import * as Settings from './modules/settings.mjs'; + + +chrome.runtime.onInstalled.addListener(function(details) { + if (details.reason === 'install') { + chrome.tabs.create({ + url: chrome.i18n.getMessage('donation_url'), + }); + } +}); + + +console.group('To intialize the extension...'); +Settings.loadCurrentSettings().then(() => { + console.log('Successfully initialized the chrome extension'); + console.groupEnd(); +}); diff --git a/src/configs/servers.mjs b/src/configs/servers.mjs new file mode 100644 index 00000000..fb00c5d0 --- /dev/null +++ b/src/configs/servers.mjs @@ -0,0 +1,4 @@ +export const DEFAULT_PROXY_PROTOCOL = 'HTTPS'; // Can be HTTP, HTTPS, SOCKS5 +export const DEFAULT_PROXY_ADDRESS = 'secure.uku.im:8443'; +export const BACKUP_PROXY_PROTOCOL = 'HTTPS'; +export const BACKUP_PROXY_ADDRESS = 'secure.uku.im:993'; diff --git a/src/configs/urls.mjs b/src/configs/urls.mjs new file mode 100644 index 00000000..04994c58 --- /dev/null +++ b/src/configs/urls.mjs @@ -0,0 +1,329 @@ +/* + * Note that the HTTPS URLs must NOT have any path portions. + * + * For example: + * - Wrong: https://example.com/abc/* + * - Right: https://example.com/* + */ + + +// The HTTP headers of these URLs will be modified. +export const HEADER_URLS = [ + 'http://*.ssports.com/*', + 'http://ssports.com/*', + 'http://ssports.smgbb.cn/*', + 'https://pstream.api.mgtv.com/*', + 'http://kandian.com/player/getEpgInfo*', +]; + + +// These URLs will not go through proxy servers. +export const PROXY_BYPASS_URLS = [ + 'http://bangumi.bilibili.com/index/ding-count.json', +]; + + +// These URLs will be proxied. +export const PROXY_URLS = [ + 'https://dmd-fifajs-h5-ikuweb.youku.com/*', + 'https://dmd-fifa-h5-ikuweb.youku.com/*', + 'http://acs.youku.com/*', + 'https://acs.youku.com/*', + // 'http://v.youku.com/player/*', + 'http://pl-ali.youku.com/*', + 'http://list.youku.com/category/*', + 'http://api.youku.com/player/*', + 'http://play.youku.com/play/get.json*', + 'http://play-dxk.youku.com/play/get.json*', + 'http://play-ali.youku.com/play/get.json*', + 'http://list.youku.com/show/module?*', + 'http://list.youku.com/show/point?*', + 'http://list.youku.com/show/episode?*', + 'http://v.youku.com/page/playlist*', + 'http://ups.youku.com/ups/get.json*', + 'https://ups.youku.com/', + 'https://ac.qq.com/*', + 'http://player.aplus.pptv.com/*', + 'http://api.aixifan.com/plays/*', + 'http://www.acfun.cn/video/getVideo*', + 'https://www.acfun.cn/*', + // 'https://v.youku.com/*', + // 'http://v2.tudou.com/*', + 'http://video.tudou.com/v/*', + 'http://www.tudou.com/a/*', + 'http://www.tudou.com/v/*', + 'http://www.tudou.com/outplay/goto/*', + 'http://www.tudou.com/tvp/alist.action*', + 'http://s.plcloud.music.qq.com/fcgi-bin/p.fcg*', + 'http://i.y.qq.com/*/fcg-bin/*', + 'http://i.y.qq.com/*/fcgi-bin/*', + 'http://c.y.qq.com/*/fcg-bin/*', + 'http://c.y.qq.com/*/fcgi-bin/*', + 'https://c.y.qq.com/*', + 'http://api.unipay.qq.com/cgi-bin/get_pay_info.fcg?*', + 'https://api.unipay.qq.com/*', + 'http://hot.vrs.sohu.com/*', + 'https://hot.vrs.sohu.com/*', + 'http://live.tv.sohu.com/live/player*', + 'http://pad.tv.sohu.com/playinfo*', + 'http://my.tv.sohu.com/play/m3u8version.do*', + 'http://hot.vrs.letv.com/*', + 'http://api.le.com/mms/out/video/*', + 'http://player.pc.le.com/mms/out/video/*', + 'http://player-pc.le.com/mms/out/video/*', + // 'http://g3.letv.cn/*', + 'http://data.video.qiyi.com/*', + 'http://data.video.iqiyi.com/*', + 'https://data.video.qiyi.com/*', + 'https://data.video.iqiyi.com/*', + // 'http://nl.rcd.iqiyi.com/apis/urc/*', + 'http://cache.vip.qiyi.com/*', + 'http://cache.video.qiyi.com/*', + 'http://cache.vip.iqiyi.com/*', + 'http://cache.video.iqiyi.com/*', + 'https://cache.video.iqiyi.com/*', + 'http://iplocation.geo.qiyi.com/cityjson*', + 'http://iplocation.geo.iqiyi.com/cityjson*', + 'http://*.cupid.iqiyi.com/*', + 'http://v.api.hunantv.com/player/video*', + 'http://mobile.api.hunantv.com/v5/video/getSource*', + 'http://v.api.mgtv.com/player/video*', + 'https://v.api.mgtv.com/*', + 'http://pcweb.api.mgtv.com/player/video*', + 'https://pcweb.api.mgtv.com/*', + 'http://acc.music.qq.com/base/fcgi-bin/getsession*', + 'http://182.254.116.117/*', + + 'http://api.appsdk.soku.com/*', + + 'http://app.bilibili.com/bangumi/*', + 'http://bangumi.bilibili.com/*', + + 'http://122.72.82.31/*', + 'http://211.151.158.155/*', + + // 'http://tt.video.qq.com/get*', + // 'http://ice.video.qq.com/get*', + // 'http://tjsa.video.qq.com/get*', + // 'http://a10.video.qq.com/get*', + // 'http://xyy.video.qq.com/get*', + // 'http://vcq.video.qq.com/get*', + // 'http://vsh.video.qq.com/get*', + // 'http://vbj.video.qq.com/get*', + // 'http://bobo.video.qq.com/get*', + // 'http://flvs.video.qq.com/get*', + // 'http://bkvv.video.qq.com/get*', + // 'http://h5vv.video.qq.com/get*', + 'http://*.video.qq.com/get*', + 'http://*.video.qq.com/fcgi-bin/*', + 'https://*.video.qq.com/*', + 'http://info.zb.qq.com/?*', + 'https://info.zb.qq.com/*', + 'http://info.zb.video.qq.com/?*', + 'https://info.zb.video.qq.com/*', + 'http://qzs.qq.com/tencentvideo_v1/*', + 'https://qzs.qq.com/*', + 'http://vd.l.qq.com/*', + 'https://vd.l.qq.com/*', + 'http://vi.l.qq.com/*', + 'https://vi.l.qq.com/*', + + 'http://dispatcher.video.sina.com.cn/*', + 'http://geo.js.kankan.com/*', + 'http://web-play.pptv.com/*', + 'http://v.pptv.com/show/*', + 'https://ppi.api.pptv.com/*', + 'http://web-play.pplive.cn/*', + 'http://tools.aplusapi.pptv.com/get_ppi?*', + 'http://live.pptv.com/api/subject_list?*', + // 'http://c1.pptv.com/*', + 'http://dyn.ugc.pps.tv/*', + 'http://v.pps.tv/ugc/ajax/aj_html5_url.php*', + 'http://inner.kandian.com/*', + 'http://ipservice.163.com/*', + 'http://so.open.163.com/open/info.htm*', + 'http://zb.s.qq.com/*', + 'https://zb.s.qq.com/*', + 'http://ip.kankan.com/*', + 'http://vxml.56.com/json/*', + + 'http://music.sina.com.cn/yueku/intro/*', + // 'http://ting.baidu.com/data/music/songlink*', + // 'http://ting.baidu.com/data/music/songinfo*', + // 'http://ting.baidu.com/song/*/download*', + 'http://music.sina.com.cn/radio/port/webFeatureRadioLimitList.php*', + 'http://play.baidu.com/data/music/songlink*', + + 'http://v.iask.com/v_play.php*', + 'http://v.iask.com/v_play_ipad.cx.php*', + 'http://tv.weibo.com/player/*', + 'http://wtv.v.iask.com/*.m3u8*', + 'http://wtv.v.iask.com/mcdn.php', + 'http://video.sina.com.cn/interface/l/u/getFocusStatus.php*', + 'http://wtv.v.iask.com/player/ovs1_idc_list.php*', + + // 'http://kandian.com/player/getEpgInfo*', // !!! + // 'http://cdn.kandian.com/*', + 'http://www.yinyuetai.com/insite/*', + 'http://www.yinyuetai.com/main/get-*', + 'http://www.kugou.com/interface/geoip/*', + 'http://www.kuwo.cn/yy/PlayCheckIp?callback=checkIpCallback&_=*', + 'http://antiserver.kuwo.cn/anti.s?*', + 'http://ipcheck.kuwo.cn/ip_check.kuwo*', + 'http://*.dpool.sina.com.cn/iplookup*', + 'http://api.letv.com/streamblock*', + 'http://api.letv.com/mms/out/video/play*', + 'http://api.www.letv.com/mms/out/video/playJson?*', + 'http://*.letv.com/mms/out/video/play*', + 'http://api.letv.com/mms/out/common/geturl*', + 'http://api.letv.com/geturl*', + 'http://api.letv.com/api/geturl*', + 'http://st.live.letv.com/live/*', + 'http://live.gslb.letv.com/gslb?*', + 'http://live.g3proxy.lecloud.com/gslb?*', + 'http://api.live.letv.com/crossdomain.xml', + 'http://static.itv.letv.com/api*', + 'http://ip.apps.cntv.cn/js/player.do*', + 'http://vdn.apps.cntv.cn/api/get*', + 'http://vdn.live.cntv.cn/api2/*', + 'http://cctv1.vtime.cntv.cloudcdn.net/cache/*', + 'http://cctv5.vtime.cntv.cloudcdn.net/cache/*', + 'http://cctv5plus.vtime.cntv.cloudcdn.net/cache/*', + 'http://cctv13.vtime.cntv.cloudcdn.net/cache/*', + 'http://sports1pull.live.wscdns.com/live/aoyun2', + 'http://vip.sports.cntv.cn/check.do*', + 'http://vip.sports.cntv.cn/play.do*', + 'http://vip.sports.cntv.cn/servlets/encryptvideopath.do*', + 'http://211.151.157.15/*', + + 'http://www.tudou.com/programs/view/*', + 'http://www.tudou.com/albumplay/*', + 'http://www.tudou.com/listplay/*', + + 'http://www.youku.com/show_page/*', + 'http://v.youku.com/v_show/*', + 'http://www.soku.com/search_video/*', + 'http://search.api.3g.youku.com/*', + 'http://search.api.3g.tudou.com/*', + 'http://*.api.tv.itc.cn/*', + 'http://api.tv.sohu.com/*', + 'http://ac.qq.com/Comic*', + 'http://ac.qq.com/Jump*', + 'http://live.api.hunantv.com/pc/getSourceById*', + 'http://mobile.api.hunantv.com/*', + 'http://www.qie.tv/*', + 'http://www.bilibili.com/video/*', + 'https://www.bilibili.com/*', + 'http://api.bilibili.com/*', + 'https://api.bilibili.com/*', + 'http://interface.bilibili.com/*', + 'https://interface.bilibili.com/*', + 'http://bangumi.bilibili.com/api/*', + 'https://bangumi.bilibili.com/*', + // 'http://live-play.acgvideo.com/live/*', + 'http://m10.music.126.net/*', // for the testing of netease music + + 'http://douban.fm/*', + 'https://douban.fm/*', + 'http://www.xiami.com/*', + 'http://lixian.xunlei.com/*', + 'http://lixian.vip.xunlei.com/*', + 'http://dynamic.cloud.vip.xunlei.com/*', + 'http://cloud.vip.xunlei.com/*', + // 'http://vod.lixian.xunlei.com/*', + 'http://www.iqiyi.com/dongman/', + 'https://www.iqiyi.com/', + + // LETV https://github.com/Unblocker/Unblock-Youku/issues/590 + 'http://36.110.222.105/*', + 'http://36.110.222.119/*', + 'http://36.110.222.146/*', + 'http://36.110.222.156/*', + 'http://123.125.89.6/*', + 'http://123.125.89.101/*', + 'http://123.125.89.102/*', + 'http://123.125.89.103/*', + 'http://123.125.89.157/*', + 'http://123.125.89.159/*', + 'http://123.126.32.134/*', + 'http://123.59.122.75/*', + 'http://123.59.122.76/*', + 'http://123.59.122.77/*', + 'http://123.59.122.104/*', + 'http://111.206.208.36/*', + 'http://111.206.208.37/*', + 'http://111.206.208.38/*', + 'http://111.206.208.61/*', + 'http://111.206.208.62/*', + 'http://111.206.208.163/*', + 'http://111.206.208.164/*', + 'http://111.206.208.166/*', + 'http://111.206.211.145/*', + 'http://111.206.211.146/*', + 'http://111.206.211.147/*', + 'http://111.206.211.148/*', + 'http://111.206.211.129/*', + 'http://111.206.211.130/*', + 'http://111.206.211.131/*', + 'http://220.181.153.113/*', + 'http://14.152.77.32/*', + 'http://14.152.77.26/*', + 'http://14.152.77.25/*', + 'http://14.152.77.22/*', + 'http://183.232.229.22/*', + 'http://183.232.229.21/*', + 'http://183.232.229.25/*', + 'http://183.232.229.32/*', + 'http://115.182.200.51/*', + 'http://115.182.200.50/*', + 'http://115.182.200.54/*', + 'http://115.182.200.53/*', + 'http://115.182.200.52/*', + 'http://115.182.63.51/*', + 'http://115.182.63.93/*', + 'http://*.letv.cn/vod/v2/*', + 'http://ark.letv.com/s*', + 'http://search.lekan.letv.com/*', + + // 'http://live.video.sina.com.cn/room/*', + // 'http://edge.v.iask.com/*', // may be large files + + 'http://pay.youku.com/buy/redirect.html*', + 'http://pay.tudou.com/buy/redirect.html*', + 'http://aid.video.qq.com/fcgi-bin/userip?*', + 'http://aidbak.video.qq.com/fcgi-bin/userip?*', + 'http://pay.video.qq.com/fcgi-bin/paylimit*', + 'http://paybak.video.qq.com/fcgi-bin/paylimit*', + 'https://*.l.qq.com/*', + 'http://chrome.2345.com/dianhua/index.php?m=call&f=check&*', + + 'http://music.163.com/eapi/*', + + // 'http://play.baidu.com/*', + // 'http://zhangmenshiting.baidu.com/*', + // 'http://music.baidu.com/box*', + // 'http://music.baidu.com/data/service/sum*', + // 'http://music.baidu.com/data/music/songlink*', + // 'http://music.baidu.com/data/music/songinfo*', + // 'http://music.baidu.com/data/music/fmlink*', + // 'http://music.baidu.com/song/*/download*', + // 'http://fm.baidu.com/*', + // 'http://www.kugou.com/*', + // 'http://music.baidu.com/data/user/collect*', + + // 'http://d.dxy.cn/*', + // 'http://ac.qq.com/*/v/cid/*', + // 'http://v.pptv.com/show/*.html', + // 'http://www.songtaste.com/*', + // 'http://songtaste.com/*', + // 'http://www.yyets.com/*', + // 'http://mainv2.img.duomi.com/*', + // 'http://imanhua.com/comic/*', + // 'http://www.imanhua.com/comic/*', + // 'http://imanhua.com/v2*', + // 'http://www.imanhua.com/v2*', + + // for development purposes + 'http://flask-test-iwauxcyxjb.cn-hangzhou.fcapp.run/*', + 'https://flask-test-iwauxcyxjb.cn-hangzhou.fcapp.run/*', +]; diff --git a/chrome/content/music.163.js b/src/content_scripts/music.163.js similarity index 100% rename from chrome/content/music.163.js rename to src/content_scripts/music.163.js diff --git a/chrome/content/play.baidu.css b/src/content_scripts/play.baidu.css similarity index 100% rename from chrome/content/play.baidu.css rename to src/content_scripts/play.baidu.css diff --git a/chrome/content/play.baidu.js b/src/content_scripts/play.baidu.js similarity index 100% rename from chrome/content/play.baidu.js rename to src/content_scripts/play.baidu.js diff --git a/chrome/content/tudou.js b/src/content_scripts/tudou.js similarity index 100% rename from chrome/content/tudou.js rename to src/content_scripts/tudou.js diff --git a/chrome/content/unblockcn.js b/src/content_scripts/unblockcn.js similarity index 100% rename from chrome/content/unblockcn.js rename to src/content_scripts/unblockcn.js diff --git a/chrome/pages/css/popup.css b/src/css/popup.css similarity index 50% rename from chrome/pages/css/popup.css rename to src/css/popup.css index a6cec8eb..70293596 100644 --- a/chrome/pages/css/popup.css +++ b/src/css/popup.css @@ -1,21 +1,3 @@ -/* - * Copyright (C) 2012 - 2014 Bo Zhu http://zhuzhu.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - - body { width: 32em; display: block; @@ -47,7 +29,6 @@ div#social-networks { font-size: 110%; } - /* mode setting section */ div#setting { margin-top: 15px; @@ -56,7 +37,7 @@ div#setting { div#buttons { margin: 15px auto; clear: both; - display: inline-block; + display: inline-block; } div#setting_description tr:last-child { @@ -70,13 +51,7 @@ div.text_block { } /* extra info and links */ -div#rating { - display: none; -} - - div#version { margin-top: 15px; text-align: center; -} - +} \ No newline at end of file diff --git a/src/modules/_header.mjs b/src/modules/_header.mjs new file mode 100644 index 00000000..f869e15c --- /dev/null +++ b/src/modules/_header.mjs @@ -0,0 +1,57 @@ +/* + * Do NOT use this module directly. Use the functions in settings.js to change settings. + */ + + +function newRandomIp() { + 'use strict'; + let ipAddr = '220.181.111.'; + // ipAddr += Math.floor(Math.random() * 255) + '.'; + ipAddr += Math.floor(Math.random() * 254 + 1); // 1 ~ 254 + return ipAddr; +} + +const RANDOM_IP = newRandomIp(); +// TODO: Store in local storage, and change everytime the chrome is restarted. + + +function headerModifier(details) { + console.log('modify headers of ' + details.url); + + details.requestHeaders.push({ + name: 'X-Forwarded-For', + value: RANDOM_IP, + }, { + name: 'Client-IP', + value: RANDOM_IP, + }); + + return {requestHeaders: details.requestHeaders}; +} + + +export function setHeaderModifier() { + if (!chrome.webRequest.onBeforeSendHeaders.hasListener(headerModifier)) { + // TODO: Fix this + // chrome.webRequest.onBeforeSendHeaders.addListener( + // headerModifier, + // { + // urls: unblock_youku.header_urls, + // }, + // ['requestHeaders', 'blocking'], + // ); + console.log('header_modifier is successfully set'); + } else { + console.error('header modifer is already there! So didn\'t set it again.'); + } +} + + +export function clearHeaderModifier() { + if (chrome.webRequest.onBeforeSendHeaders.hasListener(headerModifier)) { + chrome.webRequest.onBeforeSendHeaders.removeListener(headerModifier); + console.log('header modifier is removed'); + } else { + console.error('header modifier is not there!'); + } +} diff --git a/src/modules/_icon.mjs b/src/modules/_icon.mjs new file mode 100644 index 00000000..872b109c --- /dev/null +++ b/src/modules/_icon.mjs @@ -0,0 +1,75 @@ +/* + * Do NOT use this module directly. Use the functions in settings.js to change settings. + */ + + +import {Modes} from './modes.mjs'; + +const ICON_RELATIVE_PATH = '../../icons/'; + +// Around Spring festival +function isSpring() { + const today = new Date(); + const y = today.getFullYear(); + const m = today.getMonth() + 1; + + // Hard code some Spring festival dates + switch (y) { + case 2023: // Jan. 22, 2023 + return m === 1; + case 2024: // Feb. 10, 2024 + return m === 2; + case 2025: // Jan. 29, 2025 + return m === 1; + case 2026: // Feb. 17, 2026 + return m === 2; + case 2027: // Feb. 7, 2027 + return m === 2; + } + + return false; +} + +// Around Christmas +function isXmas() { + const today = new Date(); + const m = today.getMonth() + 1; + const d = today.getDate(); + + return m === 12 && d >= 15; +} + + +export function setIcon(mode) { + if (mode === Modes.OFF) { + chrome.action.setIcon({path: ICON_RELATIVE_PATH + 'icon19gray.png'}); + chrome.action.setBadgeText({text: 'OFF'}); + chrome.action.setTitle({title: 'Unblock Youku has been turned off.'}); + return; + } + + if (isSpring()) { + chrome.action.setIcon({path: ICON_RELATIVE_PATH + 'icon19xmas.png'}); + chrome.action.setTitle( + {title: 'Happy Spring Festival! (Unblock Youku ' + + chrome.runtime.getManifest().version + ')'}); + return; + } else if (isXmas()) { + chrome.action.setIcon({path: ICON_RELATIVE_PATH + 'icon19xmas.png'}); + chrome.action.setTitle( + {title: 'Merry Christmas! (Unblock Youku ' + + chrome.runtime.getManifest().version + ')'}); + return; + } + + chrome.action.setIcon({path: ICON_RELATIVE_PATH + 'icon19.png'}); + chrome.action.setTitle({title: 'Unblock Youku ' + chrome.runtime.getManifest().version}); +} + +export function setIconText(text) { + chrome.action.setBadgeText({text: text}); +} + +export function clearIconText() { + chrome.action.setBadgeText({text: ''}); +} diff --git a/src/modules/_proxy.mjs b/src/modules/_proxy.mjs new file mode 100644 index 00000000..e7ef5a81 --- /dev/null +++ b/src/modules/_proxy.mjs @@ -0,0 +1,70 @@ +/* + * Do NOT use this module directly. Use the functions in settings.js to change settings. + */ + + +import {PROXY_BYPASS_URLS, PROXY_URLS} from '../configs/urls.mjs'; +import { + DEFAULT_PROXY_PROTOCOL, DEFAULT_PROXY_ADDRESS, BACKUP_PROXY_PROTOCOL, BACKUP_PROXY_ADDRESS} + from '../configs/servers.mjs'; +import {urls2pac} from './_url_utils.mjs'; + + +async function setPacScript( + defaultProxyProtocol, defaultProxyAddress, backupProxyProtocol, backupProxyAddress) { + console.log('To set up proxy...'); + + const pacScript = urls2pac( + PROXY_BYPASS_URLS, + PROXY_URLS, + defaultProxyAddress, defaultProxyProtocol, + backupProxyAddress, backupProxyProtocol); + + console.group('Printing PAC script content:'); + console.log(pacScript); + console.groupEnd(); + + await chrome.proxy.settings.set({ + value: { + mode: 'pac_script', + pacScript: { + data: pacScript, + }, + }, + scope: 'regular', + }); + console.log( + 'Successfully set the proxy server: ' + defaultProxyProtocol + ' ' + defaultProxyAddress); +} + + +export function setDefaultProxy() { + return setPacScript( + DEFAULT_PROXY_PROTOCOL, DEFAULT_PROXY_ADDRESS, + BACKUP_PROXY_PROTOCOL, BACKUP_PROXY_ADDRESS); +} + + +export function setCustomProxy(customProxyProtocol, customProxyAddress) { + // Use the given proxy server as both the default and backup servers. + return setPacScript( + customProxyProtocol, customProxyAddress, + customProxyProtocol, customProxyAddress); +} + + +export async function clearProxy() { + await chrome.proxy.settings.set({ + value: { + mode: 'system', + }, + scope: 'regular', + }); + console.log('Successfully cleared the proxy (changed to system setting)'); +} + + +chrome.proxy.onProxyError.addListener(function(details) { + console.error('Received errors from onProxyError:'); + console.error(details); +}); diff --git a/src/modules/_storage.mjs b/src/modules/_storage.mjs new file mode 100644 index 00000000..9b8ff833 --- /dev/null +++ b/src/modules/_storage.mjs @@ -0,0 +1,25 @@ +/* + * Do NOT use this module directly. Use the functions in settings.js to change settings. + */ + + +export async function getItem(key) { + const items = await chrome.storage.sync.get(key); + if (typeof items !== 'undefined' && items.hasOwnProperty(key)) { + return items[key]; + } + return undefined; +} + + +export function setItem(key, value) { + // Can't just use {key: value}; otherwise the string "key" will be used as the key instead + const obj = {}; + obj[key] = value; + return chrome.storage.sync.set(obj); +} + + +export function removeItem(key) { + return chrome.storage.sync.remove(key); +} diff --git a/src/modules/_url_utils.mjs b/src/modules/_url_utils.mjs new file mode 100644 index 00000000..5fd6ae81 --- /dev/null +++ b/src/modules/_url_utils.mjs @@ -0,0 +1,238 @@ +/* + * Do NOT use this module directly. Use the functions in settings.js to change settings. + */ + + +function _parse_url(url_str) { + let protocol = null; + if (url_str.startsWith('http://')) { + url_str = url_str.slice('http://'.length); + protocol = 'http'; + } else if (url_str.startsWith('https://')) { + url_str = url_str.slice('https://'.length); + protocol = 'https'; + } else { + console.error('URL does not start with http:// or https://'); + return null; + } + + let path_idx = url_str.indexOf('/'); + if (path_idx < 0) { + path_idx = url_str.length; + url_str += '/'; + } + const colon_idx = url_str.indexOf(':'); // the colon before the optional port number + + let sep_idx = path_idx; + if (colon_idx >= 0 && colon_idx < path_idx) { + sep_idx = colon_idx; + } + + return { + protocol: protocol, + // the parameter in FindProxyForURL only doesn't contain port numbers + hostname: url_str.slice(0, sep_idx), + portpath: url_str.slice(sep_idx), + }; +} +// console.log(_parse_url('http://test.com')); +// console.log(_parse_url('http://test.com:123')); +// console.log(_parse_url('http://test.com/path')); +// console.log(_parse_url('http://test.com:123/path')); + + +function gen_url_map(protocol, white_ulist, proxy_ulist) { + 'use strict'; + const url_map = { + white: { + any: [], + }, + proxy: { + any: [], + }, + }; + + function stringify(map_obj) { + const white_map = map_obj.white; + const proxy_map = map_obj.proxy; + + let res_str = [ + '{', + ' \'white\': {', + ].join('\n') + '\n'; + res_str += stringify_patterns(white_map); + + res_str += [ + ' },', + ' \'proxy\': {', + ].join('\n') + '\n'; + res_str += stringify_patterns(proxy_map); + + res_str += [ + ' }', + '}', + ].join('\n'); + + return res_str; + } + + function stringify_patterns(hostname_map) { + let res_str = ''; + let i; let patterns = null; + + for (const hostname in hostname_map) { + if (hostname_map.hasOwnProperty(hostname)) { + res_str += ' \'' + hostname + '\': ['; + patterns = hostname_map[hostname]; + + if (patterns.length === 0) { + res_str += '],\n'; + } else { + res_str += '\n'; + for (i = 0; i < patterns.length; i++) { + res_str += ' ' + patterns[i] + ',\n'; + } + res_str = res_str.slice(0, -2) + '\n'; // remove the last , + res_str += ' ],\n'; + } + } + } + res_str = res_str.slice(0, -2) + '\n'; // remove the last , + + return res_str; + } + + function add_patterns(map_obj, ulist) { + let i; let uobj; let hostname; let portpath; + let key; let val; + for (i = 0; i < ulist.length; i++) { + uobj = _parse_url(ulist[i]); + if (uobj === null) { + console.error('Invalid URL pattern: ' + ulist[i]); + continue; + } + + if (uobj.protocol === protocol) { + hostname = uobj.hostname; + portpath = uobj.portpath; + if (hostname.indexOf('*') >= 0) { + if (hostname.slice(1).indexOf('*') >= 0) { // * is only allowed to be the first char + console.error('Invalid wildcard URL pattern: ' + ulist[i]); + continue; + } else { + key = 'any'; + val = hostname + portpath; // host:port/path + } + } else { + if (!map_obj.hasOwnProperty(hostname)) { + map_obj[hostname] = []; + } + key = hostname; + val = portpath; // only :port/path + } + + val = val.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&'); + val = val.replace(/\*/g, '.*'); + // if starts with *; should not be possible for :port or /path + val = val.replace(/^\.\*/i, '[^\/]*'); + // val = new RegExp('^' + val + '$', 'i'); + val = '/^' + val + '$/i'; + + if (val.slice(-5) === '.*$/i') { + val = val.slice(0, -5) + '/i'; + } + + map_obj[key].push(val); + } // if + } // for + } + + + add_patterns(url_map.white, white_ulist); + add_patterns(url_map.proxy, proxy_ulist); + + // console.log(stringify(url_map)); + return stringify(url_map); +} + + +export function urls2pac(url_whitelist, url_list, + proxy_server_1, proxy_protocol_1, + proxy_server_2, proxy_protocol_2) { + 'use strict'; + const http_map_str = gen_url_map('http', url_whitelist, url_list); + const https_map_str = gen_url_map('https', url_whitelist, url_list); + + if (typeof proxy_protocol_1 === 'undefined') { + proxy_protocol_1 = 'PROXY'; + } else { + proxy_protocol_1 = proxy_protocol_1.replace(/:/g, ''); + proxy_protocol_1 = proxy_protocol_1.replace(/\//g, ''); + proxy_protocol_1 = proxy_protocol_1.toUpperCase(); + if (proxy_protocol_1 === 'HTTP') { + proxy_protocol_1 = 'PROXY'; + } + } + + let _proxy_str = proxy_protocol_1 + ' ' + proxy_server_1 + '; '; + + if (typeof proxy_server_2 !== 'undefined') { + if (typeof proxy_protocol_2 === 'undefined') { + proxy_protocol_2 = 'PROXY'; + } + proxy_protocol_2 = proxy_protocol_2.replace(/:/g, ''); + proxy_protocol_2 = proxy_protocol_2.replace(/\//g, ''); + proxy_protocol_2 = proxy_protocol_2.toUpperCase(); + if (proxy_protocol_2 === 'HTTP') { + proxy_protocol_2 = 'PROXY'; + } + + _proxy_str += proxy_protocol_2 + ' ' + proxy_server_2 + '; '; + } + + _proxy_str += 'DIRECT;'; + + return [ + 'var _http_map = ' + http_map_str + ';', + 'var _https_map = ' + https_map_str + ';', + 'var _proxy_str = \'' + _proxy_str + '\';', + '', + 'function _check_regex_list(regex_list, str) {', + ' if (str.slice(0, 4) === \':80/\')', + ' str = str.slice(3);', + ' for (var i = 0; i < regex_list.length; i++)', + ' if (regex_list[i].test(str))', + ' return true;', + ' return false;', + '}', + '', + 'function _check_patterns(patterns, hostname, full_url, prot_len) {', + ' if (patterns.hasOwnProperty(hostname))', + ' if (_check_regex_list(patterns[hostname],', + ' full_url.slice(prot_len + hostname.length)))', // check only :port/path + ' return true;', + // try our best to speed up the checking for non-proxied urls + ' if (_check_regex_list(patterns.any,', + ' full_url.slice(prot_len)))', // check hostname:port/path + ' return true;', + ' return false;', + '}', + '', + 'function _find_proxy(url_map, host, url, prot_len) {', + ' if (_check_patterns(url_map.white, host, url, prot_len))', + ' return \'DIRECT\';', + ' if (_check_patterns(url_map.proxy, host, url, prot_len))', + ' return _proxy_str;', + ' return \'DIRECT\';', + '}', + '', + 'function FindProxyForURL(url, host) {', // host doesn't contain port + ' var prot = url.slice(0, 6);', + ' if (prot === \'http:/\')', + ' return _find_proxy(_http_map, host, url, 7);', // 'http://'.length + ' else if (prot === \'https:\')', + ' return _find_proxy(_https_map, host, url, 8);', // 'https://'.length + ' return \'DIRECT\';', + '}', + ].join('\n') + '\n'; +} diff --git a/src/modules/modes.mjs b/src/modules/modes.mjs new file mode 100644 index 00000000..1c263b57 --- /dev/null +++ b/src/modules/modes.mjs @@ -0,0 +1,5 @@ +// Enum definitions for which mode is the extension currently in +export const Modes = Object.freeze({ + OFF: 'off', + NORMAL: 'normal', +}); diff --git a/src/modules/settings.mjs b/src/modules/settings.mjs new file mode 100644 index 00000000..a64e21cd --- /dev/null +++ b/src/modules/settings.mjs @@ -0,0 +1,120 @@ +import {Modes} from './modes.mjs'; +import * as Storage from './_storage.mjs'; +import * as Proxy from './_proxy.mjs'; +import * as Icon from './_icon.mjs'; + + +const MODE_STORAGE_KEY = 'unblock_youku_mode'; +const CUSTOM_PROXY_STORAGE_KEY = 'custom_proxy_server'; + +export async function getCurrentMode() { + const mode = await Storage.getItem(MODE_STORAGE_KEY); + switch (mode) { + case Modes.OFF: + // Fall through + case Modes.NORMAL: + return mode; + + default: + console.warn('Got unknown operation mode: ' + mode + '. Resetting to normal mode.'); + // Overwrite with default mode + await Storage.setItem(MODE_STORAGE_KEY, Modes.NORMAL); + return Modes.NORMAL; + } +} + +export function getCustomProxy() { + return Storage.getItem(CUSTOM_PROXY_STORAGE_KEY); +} + + +// Clear all settings regardless of the current mode +// Note: Make sure a Promise is returned so that the caller can wait for it +function clearAllSettings() { + return Promise.all([ + Proxy.clearProxy(), + // Header.clearHeaderModifier(), + ]); +} + +// Apply the settings for the given mode +// Note: Make sure a Promise is returned so that the caller can wait for it +async function applyModeSettings(mode) { + if (mode === Modes.OFF) { + Icon.setIcon(Modes.OFF); + return; // Do nothing else + } + + const customProxy = await getCustomProxy(); + if (typeof customProxy === 'undefined' || + typeof customProxy.protocol === 'undefined' || + typeof customProxy.address === 'undefined') { + await Proxy.setDefaultProxy(); + } else { + await Proxy.setCustomProxy(customProxy.protocol, customProxy.address); + } + // TODO: Set header modifier + Icon.setIcon(Modes.NORMAL); +} + +// ================================================================ +// Function called by background.js: +// * reloadCurrentSettings() +// 1. Load the latest config from storage +// 2. Apply all settings for the Chrome extension + +export async function loadCurrentSettings() { + const mode = await getCurrentMode(); + await applyModeSettings(mode); + console.log('Successfully loaded all the settings'); +} + + +// ================================================================ +// Function called by popup.js: +// * setNewMode() +// 1. Tear down the settings of the old mode +// 2. Set up the mode settings for the new mode +// 3. Save the new mode into storage + +export async function setNewMode(mode) { + await clearAllSettings(); + await applyModeSettings(mode); + await Storage.setItem(MODE_STORAGE_KEY, mode); + + // Set this one-time text when the user changes the mode + if (mode == Modes.OFF) { + Icon.setIconText('OFF'); + } else { + Icon.clearIconText(); + } + console.log('Successfully set the mode to ' + mode); +} + + +// ================================================================ +// Functions called by options.js +// * setCustomProxy() +// 1. Save the custom proxy server configs into storage +// 2. Change mode to use proxy, and apply settings with the custom proxy server +// * clearCustomProxy() +// 1. Remove the custom proxy server configs from storage +// 2. Change mode to use proxy, and apply settings with the default proxy server + + +export async function setCustomProxy(customProxyProtocol, customProxyAddress) { + await Storage.setItem(CUSTOM_PROXY_STORAGE_KEY, { + protocol: customProxyProtocol, + address: customProxyAddress, + }); + await setNewMode(Modes.NORMAL); // Change the mode to use the proxy right away + console.log( + 'Successfully set the custom proxy server to ' + + customProxyProtocol + ' ' + customProxyAddress); +} + +export async function clearCustomProxy() { + await Storage.removeItem(CUSTOM_PROXY_STORAGE_KEY); + await setNewMode(Modes.NORMAL); // Change the mode to use the proxy right away + console.log('Successfully cleared the custom proxy server'); +} diff --git a/chrome/pages/options.html b/src/options.html similarity index 76% rename from chrome/pages/options.html rename to src/options.html index d8b543f5..b6c2e26d 100644 --- a/chrome/pages/options.html +++ b/src/options.html @@ -1,20 +1,22 @@ + Options for Unblock Youku - - - - - + + + + +
- Custom backend server for the Proxy Mode + Custom backend server for the Proxy + Mode
@@ -34,7 +36,8 @@
- +
@@ -42,7 +45,7 @@
-
+
- + \ No newline at end of file diff --git a/src/options.mjs b/src/options.mjs new file mode 100644 index 00000000..bbd1e1b4 --- /dev/null +++ b/src/options.mjs @@ -0,0 +1,108 @@ +import * as Settings from './modules/settings.mjs'; +import {DEFAULT_PROXY_PROTOCOL, DEFAULT_PROXY_ADDRESS} from '../configs/servers.mjs'; + + +function showProxyMessage(type, content) { + let alertType = 'info'; + if (type === 'success' || type === 'warning') { + alertType = type; // success, info, or warning + } + + $('#proxy_message').html( + '
' + + '' + + content + + '
'); +} + + +function getCustomProxyServer(callback) { + background.get_storage('custom_proxy_server', function(serverInfo) { + if (typeof serverInfo === 'undefined' || + typeof serverInfo.proc === 'undefined' || + typeof serverInfo.addr === 'undefined') { + callback(/* customEnabled= */ false, DEFAULT_PROXY_PROTOCOL, DEFAULT_PROXY_ADDRESS); + } else { + callback(/* customEnabled= */ true, serverInfo.proc, serverInfo.addr); + } + }); +} + + +$(document).ready(function() { + getCustomProxyServer(function(customEnabled, serverProtocol, serverAddress) { + $('#custom_proxy_proc option').each(function(idx, d) { + if (d.value === serverProtocol) { + d.selected = true; + } + }); + $('#custom_proxy_addr').val(serverAddress); + if (customEnabled === true) { + $('#custom_proxy_proc').attr('disabled', true); + $('#custom_proxy_addr').attr('disabled', true); + $('#custom_proxy_enable').attr('disabled', true); + background.get_mode_name(function(currentMode) { + if (currentMode === 'normal') { + showProxyMessage('info', 'Status: Enabled'); + } else { + showProxyMessage('warning', 'Status: Enabled. But current mode is not proxy mode.'); + } + }); + } else { + showProxyMessage('info', 'Status: NOT Enabled'); + $('#custom_proxy_reset').attr('disabled', true); + } + }); + + $('#custom_proxy_enable').click(function() { + const customProxyProtocol = $('#custom_proxy_proc').val(); + const customProxyAddress = $('#custom_proxy_addr').val(); + $('#custom_proxy_proc').attr('disabled', true); + $('#custom_proxy_addr').attr('disabled', true); + $('#custom_proxy_enable').attr('disabled', true); + set_custom_proxy_server(customProxyProtocol, customProxyAddress, function() { + // switch to proxy mode and force refresh pac proxy + background.get_mode_name(function(currentMode) { + if (currentMode === 'normal') { + background.setup_proxy(); + $('#custom_proxy_reset').attr('disabled', false); + showProxyMessage('info', 'Enabled custom proxy server.'); + } else { + background.set_mode_name('normal', function() { + $('#custom_proxy_reset').attr('disabled', false); + showProxyMessage( + 'warning', 'Enabled custom proxy server, and changed to proxy mode.'); + }); + } + }); + }); + }); + + $('#custom_proxy_reset').click(function() { + $('#custom_proxy_reset').attr('disabled', true); + remove_custom_proxy_server(function() { + // switch to proxy mode and force refresh pac proxy + background.get_mode_name(function(currentMode) { + if (currentMode === 'normal') { + background.setup_proxy(); + $('#custom_proxy_proc').attr('disabled', false); + $('#custom_proxy_addr').attr('disabled', false); + $('#custom_proxy_enable').attr('disabled', false); + showProxyMessage('warning', 'Reset custom proxy server.'); + } else { + background.set_mode_name('normal', function() { + $('#custom_proxy_proc').attr('disabled', false); + $('#custom_proxy_addr').attr('disabled', false); + $('#custom_proxy_enable').attr('disabled', false); + showProxyMessage('warning', 'Reset custom proxy server, and changed to proxy mode.'); + }); + } + }); + }); + }); + + $('#form_custom_proxy_server').submit(function(event) { + // prevent the default action of submitting a form + event.preventDefault(); + }); +}); diff --git a/chrome/pages/popup.html b/src/popup.html similarity index 78% rename from chrome/pages/popup.html rename to src/popup.html index 03f71f04..c0940b87 100644 --- a/chrome/pages/popup.html +++ b/src/popup.html @@ -1,24 +1,25 @@ + Popup Page for Unblock Youku - - + + - - - - + + + +
-
NEW
+
NEW

- +
@@ -66,9 +67,9 @@
-
+
- + - + \ No newline at end of file diff --git a/src/popup.mjs b/src/popup.mjs new file mode 100644 index 00000000..3aef4e38 --- /dev/null +++ b/src/popup.mjs @@ -0,0 +1,62 @@ +import {Modes} from './modules/modes.mjs'; +import * as Settings from './modules/settings.mjs'; +import * as Icon from './modules/_icon.mjs'; + + +function setTranslatedTexts() { + const getMsg = chrome.i18n.getMessage; + + $('div#support strong').html(getMsg('support_title')); + $('p#support_message').html(getMsg('support_message')); + $('a#support_link').attr('href', getMsg('donation_url')); + $('button#support_button').html(getMsg('support_button')); + + $('div#social strong').html(getMsg('social_title')); + + $('div#mode_select strong').html(getMsg('mode_select')); + + $('span.mode_off_name').html(getMsg('mode_off')); + $('span.mode_off_desc').html(getMsg('mode_off_description')); + $('span.mode_normal_name').html(getMsg('mode_normal')); + $('span.mode_normal_desc').html(getMsg('mode_normal_description')); + + $('div#faq').html(getMsg('faq')); + $('div#feedback').html(getMsg('feedback')); + $('div#rating').html(getMsg('rating')); +} + + +$(document).ready(function() { + setTranslatedTexts(); + + // Set default button display + Settings.getCurrentMode().then((mode) => { + switch (mode) { + case Modes.OFF: + $('label#off').addClass('active'); + break; + default: + $('label#normal').addClass('active'); + break; + } + }); + + // Add version number to the footer + $('div#version small').html('Unblock Youku v' + chrome.runtime.getManifest().version); + // Clear the text on the browser icon after the user has clicked on the icon + Icon.clearIconText(); + + // Set up button actions + $('input#input_off').change(function() { + console.group('Clicked on the button to change the mode to OFF...'); + Settings.setNewMode(Modes.OFF).then(console.groupEnd); + }); + $('input#input_normal').change(function() { + console.group('Clicked on the button to change the mode to NORMAL...'); + Settings.setNewMode(Modes.NORMAL).then(console.groupEnd); + }); + + // Enable tooltip + $('#tooltip').tooltip(); +}); + diff --git a/chrome/pages/css/bootstrap-3.3.5.min.css b/src/third_party/css/bootstrap-3.3.5.min.css similarity index 100% rename from chrome/pages/css/bootstrap-3.3.5.min.css rename to src/third_party/css/bootstrap-3.3.5.min.css diff --git a/chrome/pages/css/font-awesome-4.4.0.min.css b/src/third_party/css/font-awesome-4.4.0.min.css similarity index 100% rename from chrome/pages/css/font-awesome-4.4.0.min.css rename to src/third_party/css/font-awesome-4.4.0.min.css diff --git a/chrome/pages/fonts/fontawesome-webfont.woff2 b/src/third_party/fonts/fontawesome-webfont.woff2 similarity index 100% rename from chrome/pages/fonts/fontawesome-webfont.woff2 rename to src/third_party/fonts/fontawesome-webfont.woff2 diff --git a/chrome/pages/fonts/glyphicons-halflings-regular.woff2 b/src/third_party/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from chrome/pages/fonts/glyphicons-halflings-regular.woff2 rename to src/third_party/fonts/glyphicons-halflings-regular.woff2 diff --git a/chrome/pages/js/bootstrap-3.3.5.min.js b/src/third_party/js/bootstrap-3.3.5.min.js similarity index 100% rename from chrome/pages/js/bootstrap-3.3.5.min.js rename to src/third_party/js/bootstrap-3.3.5.min.js diff --git a/chrome/pages/js/jquery-2.1.4.min.js b/src/third_party/js/jquery-2.1.4.min.js similarity index 100% rename from chrome/pages/js/jquery-2.1.4.min.js rename to src/third_party/js/jquery-2.1.4.min.js diff --git a/tools/produce_regex.mjs b/tools/produce_regex.mjs new file mode 100644 index 00000000..20203e68 --- /dev/null +++ b/tools/produce_regex.mjs @@ -0,0 +1,48 @@ +import {PROXY_URLS} from '../src/configs/urls.mjs'; + + +function urls2regexs(urlList) { + const regexList = []; + + for (let str of urlList) { + // Escape all possibly problematic symbols + // http://stackoverflow.com/a/6969486/1766096 + str = str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&'); + str = str.replace(/\*/g, '.*'); + + // make the first * matches only domain names or ip addresses + // just as http://developer.chrome.com/extensions/match_patterns.html + str = str.replace(/^http:\\\/\\\/\.\*/i, 'http:\\/\\/[^\/]*'); + str = str.replace(/^https:\\\/\\\/\.\*/i, 'https:\\/\\/[^\/]*'); + + regexList.push(new RegExp('^' + str + '$', 'i')); + } + + // console.log(regex_list); + return regexList; +} + + +function produceSquidRegexList() { + const regexList = urls2regexs(PROXY_URLS); + const regexToExtractHttpsDomain = /^\^https:\\\/\\\/([^:]+)\\\//i; + + let str; + const result = []; + for (const regex of regexList) { + str = regex.toString(); + str = str.substring(1, str.length - 2); + + if (str.match(regexToExtractHttpsDomain)) { + str = '^' + str.match(regexToExtractHttpsDomain)[1] + ':443'; + } + + result.push(str); + } + + return result; +} + + +console.log(produceSquidRegexList().join('\n') + '\n'); +process.exit(0);