Showing preview only (264K chars total). Download the full file or copy to clipboard to get everything.
Repository: EtherDream/jsproxy-browser
Branch: master
Commit: a7a6d740e2c6
Files: 49
Total size: 242.9 KB
Directory structure:
gitextract_qe_rn8jq/
├── .gitignore
├── .vscode/
│ ├── launch.json
│ └── tasks.json
├── LICENSE
├── README.md
├── src/
│ ├── home/
│ │ ├── 404.html
│ │ ├── README.md
│ │ ├── assets/
│ │ │ ├── cors_v1.txt
│ │ │ └── index_v3.html
│ │ ├── build.sh
│ │ ├── conf.js
│ │ └── sw.js
│ └── proxy/
│ ├── .eslintrc.json
│ ├── .package-lock.json
│ ├── .package.json
│ ├── README.md
│ ├── debug.sh
│ ├── gen-tld/
│ │ ├── README.md
│ │ └── gen.js
│ ├── jsconfig.json
│ ├── release.sh
│ └── src/
│ ├── cdn.js
│ ├── client.js
│ ├── cookie.js
│ ├── database.js
│ ├── env.js
│ ├── fakeloc.js
│ ├── hook.js
│ ├── index.js
│ ├── inject.js
│ ├── jsfilter.js
│ ├── msg.js
│ ├── network.js
│ ├── page.js
│ ├── path.js
│ ├── route.js
│ ├── signal.js
│ ├── storage.js
│ ├── sw.js
│ ├── tld-data.js
│ ├── tld.js
│ ├── urlx.js
│ └── util.js
└── www/
├── 404.html
├── assets/
│ ├── README.md
│ ├── cors_v1.txt
│ └── index_v3.html
├── conf.js
└── sw.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
._*
node_modules
src/home/assets/bundle.debug.js
www/assets/bundle.*.js
================================================
FILE: .vscode/launch.json
================================================
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "clang build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
"preLaunchTask": "clang build active file"
}
]
}
================================================
FILE: .vscode/tasks.json
================================================
{
"tasks": [
{
"type": "shell",
"label": "clang build active file",
"command": "/usr/bin/clang",
"args": [
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "/usr/bin"
}
}
],
"version": "2.0.0"
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 EtherDream
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
JsProxy 浏览器端程序
# 修改配置
修改 `www/conf.js` 配置,发布 `www` 目录即可。
## TODO
* 增加网络错误重试、优先选择空闲节点功能
* 在 SW 中替换 HTML 的 URL 属性,并支持流模式
* CDN 文件使用 brotli 压缩
* 使用 AST 修改 JS 代码
* 动态页面压缩传输(反模板引擎,只传输变量,模板本身存储在 CDN)
* 使用更多的免费图床作为 CDN 资源存储,并支持 Hash 校验
* 计算程序使用 wasm 实现
* 支持 blob/data/javascript 协议
* 增加 qos 功能,优先满足资料查询网站流量
* 改进同源策略的安全性,增加部分 API 的授权界面
* 重新设计首页,增加更多功能
* 完整的测试案例
# 已知问题
* 文件下载对话框取消后 SW 仍在下载(fetch.signal 的 onabort 未触发,可能是浏览器 BUG)
* Chrome 图片无法保存
* 非 UTF8 编码的 JS 会出现乱码(MIME 未指定 charset 的情况下出现)
* Google 登陆页无法下一步
* Google reCAPTCHA 无法执行
* Google Maps 切换到卫星地图后卡死
* iOS Safari 无法播放 Youtube 视频
* twitter 在 Chrome 普通模式下无法登陆,但隐身模式可以
* twitter iframe 经常加载不出来
* SVG 脚本没有处理
* Youtube 视频全屏播放会卡住
* twitch.tv 首页报错(JS 代码修改导致错误,需要在 AST 层面修改)
================================================
FILE: src/home/404.html
================================================
<!--
该页面用于安装 Service Worker
用户首次访问时,无论请求哪个资源(包括首页),都返回该页面
该页面尽可能精简,兼容所有浏览器
-->
<html>
<head>
<meta charset="utf-8">
</head>
<body id="t">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" fill="none"><circle cx="50" cy="50" r="25.944" stroke="#93dbe9"><animate attributeName="r" calcMode="spline" values="0;40" keyTimes="0;1" dur="1" keySplines="0 0.2 0.8 1" begin="-0.5s" repeatCount="indefinite" stroke-width="2"/><animate attributeName="opacity" calcMode="spline" values="1;0" keyTimes="0;1" dur="1" keySplines="0.2 0 0.8 1" begin="-0.5s" repeatCount="indefinite" stroke-width="2"/></circle><circle cx="50" cy="50" r="40" stroke="#689cc5"><animate attributeName="r" calcMode="spline" values="0;40" keyTimes="0;1" dur="1" keySplines="0 0.2 0.8 1" begin="0s" repeatCount="indefinite" stroke-width="2"/><animate attributeName="opacity" calcMode="spline" values="1;0" keyTimes="0;1" dur="1" keySplines="0.2 0 0.8 1" begin="0s" repeatCount="indefinite" stroke-width="2"/></circle></svg>
<script>
function reload() {
// 设置最小刷新间隔,防止出错时不停刷新
var curr = Date.now()
try {
var last = +sessionStorage._ts || 0
sessionStorage._ts = curr
if (curr - last < 100) {
return setTimeout(reload, 5000)
}
} catch (err) {
}
location.reload()
}
function onfail(err) {
showErr(err.message)
}
function showErr(str) {
t.innerHTML = str
}
function getRootPath() {
// https://a.com/ -> /
// https://a.com/index.html -> /
// https://a.com/path/to/ -> /path/to/
// https://a.com/path/to -> /path/to/
// https://a.com/path/to/index.html -> /path/to/
return location.pathname
.replace(/\/-+https?:.+/, '')
.replace(/\w+\.\w+$/, '')
.replace(/\/*$/, '/')
}
function main() {
if (!self.isSecureContext) {
showErr('本程序需要 HTTPS 站点')
return
}
if (!self.ReadableStream) {
showErr('请使用最新的 HTML5 浏览器')
return
}
var sw = navigator.serviceWorker
if (!sw) {
var ua = navigator.userAgent
if (/Firefox/.test(ua)) {
showErr('本网站不支持 FireFox 隐身模式')
}
else if (/iPhone|iPad/.test(ua)) {
showErr('iOS 上请使用 Safari 浏览器')
}
return
}
sw
.register(getRootPath() + 'sw.js')
.then(reload)
.catch(onfail)
}
main()
</script>
</body>
</html>
================================================
FILE: src/home/README.md
================================================
## 404.html
该页面用于安装 Service Worker。
因为本项目只有一个 404.html,所以访问任意路径都会触发该页面。通过该页面安装 Service Worker 然后自动刷新,后续所有流量都可被 Service Worker 接管。
## sw.js
Service Worker 脚本。
虽然 Web 标准规定 Service Worker 脚本必须和站点同源,但是 JS 可通过 `importScripts` 加载站外脚本。因此可将主体代码部署在 CDN,该脚本仅仅作为一个加载器。
## build.sh
压缩当前目录 404.html 到 ../../www/404.html
## conf.js
配置文件。该文件首次运行时动态加载
## assets
该目录存放静态资源,可部署到 CDN。
================================================
FILE: src/home/assets/cors_v1.txt
================================================
# HTTP 返回头存在 access-control-allow-origin: * 的站点,不走代理直接连接
# 收集了部分,实验中...
# google
ssl.google-analytics.com
# [public]
cdn.jsdelivr.net
unpkg.com
cdnjs.cloudflare.com
cdn.bootcss.com
use.fontawesome.com
fast.fonts.net
script.hotjar.com
# github
github.githubassets.com
avatars0.githubusercontent.com
avatars1.githubusercontent.com
avatars2.githubusercontent.com
avatars3.githubusercontent.com
desktop.github.com
# flickr
status.flickr.net
# ali
at.alicdn.com
img.alicdn.com
g.alicdn.com
i.alicdn.com
atanx.alicdn.com
wwc.alicdn.com
gw.alicdn.com
assets.alicdn.com
aeis.alicdn.com
atanx.alicdn.com
hudong.alicdn.com
gma.alicdn.com
sc01.alicdn.com
sc02.alicdn.com
sc03.alicdn.com
sc04.alicdn.com
cbu01.alicdn.com
cbu02.alicdn.com
cbu03.alicdn.com
cbu04.alicdn.com
# baidu
# img*.bdimg.com
img0.bdimg.com
img1.bdimg.com
img2.bdimg.com
img3.bdimg.com
img4.bdimg.com
img5.bdimg.com
webmap0.bdimg.com
webmap1.bdimg.com
iknowpc.bdimg.com
bkssl.bdimg.com
baikebcs.bdimg.com
gh.bdstatic.com
# qq
3gimg.qq.com
combo.b.qq.com
# taotiao
images.taboola.com
images.taboola.com.cn
images-dup.taboola.com
# zhihu
static.zhihu.com
pic1.zhimg.com
pic2.zhimg.com
pic3.zhimg.com
pic4.zhimg.com
pic5.zhimg.com
pic7.zhimg.com
# jd
img11.360buyimg.com
# jianshu
upload.jianshu.io
upload-images.jianshu.io
cdn2.jianshu.io
# 163
urswebzj.nosdn.127.net
static.ws.126.net
img1.cache.netease.com
img2.cache.netease.com
img3.cache.netease.com
img4.cache.netease.com
img5.cache.netease.com
img6.cache.netease.com
# sina
js.t.sinajs.cn
mjs.sinaimg.cn
h5.sinaimg.cn
# sohu
0d077ef9e74d8.cdn.sohucs.com
39d0825d09f05.cdn.sohucs.com
5b0988e595225.cdn.sohucs.com
caaceed4aeaf2.cdn.sohucs.com
img01.sogoucdn.com
img02.sogoucdn.com
img03.sogoucdn.com
img04.sogoucdn.com
img05.sogoucdn.com
# hupu
w1.hoopchina.com.cn
w2.hoopchina.com.cn
w3.hoopchina.com.cn
w4.hoopchina.com.cn
shihuo.hupucdn.com
# uc
image.uc.cn
# ...
static.cnodejs.org
static2.cnodejs.org
2b.zol-img.com.cn
img.pconline.com.cn
angular.cn
img1.dxycdn.com
cdn.kastatic.org
static.geetest.com
cdn.registerdisney.go.com
secure-us.imrworldwide.com
img1.doubanio.com
qnwww2.autoimg.cn
qnwww3.autoimg.cn
s.autoimg.cn
hb.imgix.net
main.qcloudimg.com
vz-cdn2.contentabc.com
twemoji.maxcdn.com
fgn.cdn.serverable.com
s1.hdslb.com
s2.hdslb.com
s3.hdslb.com
# cnblogs
common.cnblogs.com
mathjax.cnblogs.com
# csdn
csdnimg.cn
g.csdnimg.cn
img-ads.csdn.net
img-bss.csdn.net
img-blog.csdn.net
# ...
static.geekbang.org
static001.infoq.cn
static.docs.com
cdn1.developermedia.com
cdn2.developermedia.com
cdn.optimizely.com
cdn.ampproject.org
camshowverse.to
static.camshowhub-cdn.to
xqimg.imedao.com
xavatar.imedao.com
# ???
img-l3.xvideos-cdn.com
static-egc.xvideos-cdn.com
img-hw.xvideos-cdn.com
img-hw.xnxx-cdn.com
static-egc.xnxx-cdn.com
di.phncdn.com
cv.phncdn.com
roomimg.stream.highwebmedia.com
w3.cdn.anvato.net
================================================
FILE: src/home/assets/index_v3.html
================================================
<!doctype html>
<html>
<head>
<title>Page Sandbox</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<base target="_blank">
<style>
body {
padding: 0 0.5em;
}
.box {
margin-top: 1em;
}
#txtURL {
width: 100%;
height: 2em;
text-indent: 0.5em;
padding: 0.25em 0;
}
#btnGo {
width: 100%;
font-size: 1.5em;
}
#list a {
margin: 1em;
}
</style>
</head>
<body>
<div class="box">
<input id="txtURL" type="text" value="www.google.com" autofocus>
</div>
<div class="box">
<button id="btnGo">Go</button>
</div>
<div class="box">
<span>切换线路:</span>
<select id="selNode"></select>
</div>
<div class="box" id="list">
</div>
<script>
const PAGE_CONF_SET = 110
const PAGE_CONF_GET = 111
const SW_CONF_RETURN = 112
const SW_CONF_CHANGE = 113
const PAGE_READY_CHECK = 200
const SW_READY = 201
const sw = navigator.serviceWorker
sw.addEventListener('message', onSwMsg)
sendMsgToSw(PAGE_READY_CHECK)
btnGo.onclick = function() {
const text = txtURL.value.trim()
if (text) {
const url = './-----' + text
open(url, '_blank', 'noopener,noreferrer')
}
}
txtURL.onkeypress = function(e) {
if (e.keyCode === 13) {
btnGo.onclick()
}
}
txtURL.setSelectionRange(0, txtURL.value.length)
function onSwMsg(e) {
const [cmd, msg] = e.data
switch (cmd) {
case SW_CONF_RETURN:
conf = msg
showConf()
break
case SW_CONF_CHANGE:
conf = msg
updateSelected()
break
case SW_READY:
console.log('sw ready')
showIcons()
sendMsgToSw(PAGE_CONF_GET)
break
}
}
function onSwFail(err) {
txtURL.value = err
}
selNode.onchange = function() {
const item = this.options[this.selectedIndex]
const node = item.value
conf.node_default = node
sendMsgToSw(PAGE_CONF_SET, conf)
}
function sendMsgToSw(cmd, val) {
const ctl = sw.controller
if (!ctl) {
console.log('ctl is null')
return
}
ctl.postMessage([cmd, val])
}
const SITE_LIST = [
['google', ''],
['youtube', ''],
['twitter', 'twitter.com/google'],
['flickr', ''],
['quora', 'www.quora.com/topic/JavaScript-programming-language'],
['twitch', 'www.twitch.tv/'],
['reddit', ''],
['wiki', 'zh.wikipedia.org/'],
['gist', 'gist.github.com/'],
['facebook', 'facebook.com/Google/'],
['blogger', ''],
]
function showIcons() {
list.innerHTML = SITE_LIST.map(v => {
let [id, url] = v
url = url || `www.${id}.com/`
return `\
<a rel="noopener noreferrer" href=./-----https://${url}>\
<img width=128 height=128 src=__sys__/assets/ico/${id}.png></a>`
}).join('')
}
function addNodeItem(id, text) {
const optEl = document.createElement('option')
optEl.id = '--' + id
optEl.text = text
optEl.value = id
selNode.appendChild(optEl)
}
function updateSelected() {
const id = conf.node_default
const item = document.getElementById('--' + id)
if (item) {
item.selected = true
} else {
console.warn('unknown node:', id)
}
}
function showConf() {
for (const [id, node] of Object.entries(conf.node_map)) {
if (!node.hidden) {
addNodeItem(id, node.label)
}
}
updateSelected()
}
</script>
</body>
</html>
================================================
FILE: src/home/build.sh
================================================
# 压缩 404.html
html-minifier \
--collapse-whitespace \
--remove-comments \
--remove-redundant-attributes \
--remove-script-type-attributes \
--remove-tag-whitespace \
--use-short-doctype \
--remove-attribute-quotes \
--minify-css true \
--minify-js '{"toplevel": true, "ie8": true}' \
-o ../../www/404.html \
404.html
================================================
FILE: src/home/conf.js
================================================
jsproxy_config({
// 当前配置的版本(记录在日志中,用于排查问题)
// 每次修改配置,该值需要增加,否则不会生效。
// 默认每隔 5 分钟自动下载配置,若想立即验证,可通过隐私模式访问。
ver: '105',
// 通过 CDN 加速常用网站的静态资源(实验中)
static_boost: {
enable: true,
ver: 56
},
// 节点配置
node_map: {
'demo-hk': {
label: '演示服务-香港节点',
lines: {
// 主机:权重
'node-aliyun-hk-0.etherdream.com:8443': 1,
'node-aliyun-hk-1.etherdream.com:8443': 1,
'node-aliyun-hk-2.etherdream.com:8443': 1,
}
},
'demo-sg': {
label: '演示服务-新加坡节点',
lines: {
'node-aliyun-sg.etherdream.com:8443': 1,
},
},
'demo-la': {
label: '演示服务-洛杉矶节点',
lines: {
'node-bwh-la.etherdream.com:8443': 1,
},
},
'mysite': {
label: '当前站点',
lines: {
[location.host]: 1,
}
},
// 该节点用于加载大体积的静态资源
'cfworker': {
label: '',
hidden: true,
lines: {
'node-cfworker-2.etherdream.com': 1,
}
}
},
/**
* 默认节点
*/
node_default: 'demo-hk',
/**
* 加速节点
*/
node_acc: 'cfworker',
/**
* 静态资源 CDN 地址
* 用于加速 `assets` 目录中的资源访问
*/
// assets_cdn: 'https://cdn.jsdelivr.net/gh/zjcqoo/zjcqoo.github.io@master/assets/',
// 本地测试时打开,否则访问的是线上的
assets_cdn: 'assets/',
// 首页路径
index_path: 'index_v3.html',
// 支持 CORS 的站点列表(实验中...)
direct_host_list: 'cors_v1.txt',
/**
* 自定义注入页面的 HTML
*/
inject_html: '<!-- custom html -->',
/**
* URL 自定义处理(设计中)
*/
url_handler: {
'https://www.baidu.com/img/baidu_resultlogo@2.png': {
replace: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png'
},
'https://www.pornhub.com/': {
redir: 'https://php.net/'
},
'http://haha.com/': {
content: 'Hello World'
},
}
})
================================================
FILE: src/home/sw.js
================================================
jsproxy_config=x=>{__CONF__=x;importScripts(__FILE__=x.assets_cdn+'bundle.debug.js')};importScripts('conf.js')
================================================
FILE: src/proxy/.eslintrc.json
================================================
{
"env": {
"browser": true,
"es6": true,
"serviceworker": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"rules": {
"no-console": "warn",
"no-empty": "warn",
"no-unused-vars": "warn",
"no-debugger": "warn",
"no-constant-condition": "warn"
}
}
================================================
FILE: src/proxy/.package-lock.json
================================================
{
"name": "jsproxy-client",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
},
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"dev": true
},
"acorn-jsx": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
"integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
"dev": true
},
"ajv": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"astral-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"dev": true,
"requires": {
"restore-cursor": "^2.0.0"
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
"dev": true
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"eslint": {
"version": "5.16.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
"integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"ajv": "^6.9.1",
"chalk": "^2.1.0",
"cross-spawn": "^6.0.5",
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"eslint-scope": "^4.0.3",
"eslint-utils": "^1.3.1",
"eslint-visitor-keys": "^1.0.0",
"espree": "^5.0.1",
"esquery": "^1.0.1",
"esutils": "^2.0.2",
"file-entry-cache": "^5.0.1",
"functional-red-black-tree": "^1.0.1",
"glob": "^7.1.2",
"globals": "^11.7.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"inquirer": "^6.2.2",
"js-yaml": "^3.13.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0",
"lodash": "^4.17.11",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"path-is-inside": "^1.0.2",
"progress": "^2.0.0",
"regexpp": "^2.0.1",
"semver": "^5.5.1",
"strip-ansi": "^4.0.0",
"strip-json-comments": "^2.0.1",
"table": "^5.2.3",
"text-table": "^0.2.0"
}
},
"eslint-scope": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
"integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"eslint-utils": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
"dev": true
},
"eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
"dev": true
},
"espree": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
"integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
"dev": true,
"requires": {
"acorn": "^6.0.7",
"acorn-jsx": "^5.0.0",
"eslint-visitor-keys": "^1.0.0"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esquery": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
"integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
"dev": true,
"requires": {
"estraverse": "^4.0.0"
}
},
"esrecurse": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
"dev": true,
"requires": {
"estraverse": "^4.1.0"
}
},
"estraverse": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
"dev": true
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
"external-editor": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
"integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
"dev": true,
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
}
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
"dev": true
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"file-entry-cache": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
"integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
"dev": true,
"requires": {
"flat-cache": "^2.0.1"
}
},
"flat-cache": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
"integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
"dev": true,
"requires": {
"flatted": "^2.0.0",
"rimraf": "2.6.3",
"write": "1.0.3"
}
},
"flatted": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
"integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"import-fresh": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
"integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"inquirer": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz",
"integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==",
"dev": true,
"requires": {
"ansi-escapes": "^3.2.0",
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": "^4.17.11",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.4.0",
"string-width": "^2.1.0",
"strip-ansi": "^5.1.0",
"through": "^2.3.6"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"optionator": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
"wordwrap": "~1.0.0"
}
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-is-inside": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
"integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
"dev": true
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"dev": true,
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
"dev": true,
"requires": {
"is-promise": "^2.1.0"
}
},
"rxjs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz",
"integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"dev": true
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"slice-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"astral-regex": "^1.0.0",
"is-fullwidth-code-point": "^2.0.0"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"table": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz",
"integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==",
"dev": true,
"requires": {
"ajv": "^6.9.1",
"lodash": "^4.17.11",
"slice-ansi": "^2.1.0",
"string-width": "^3.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
"integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
"dev": true,
"requires": {
"mkdirp": "^0.5.1"
}
}
}
}
================================================
FILE: src/proxy/.package.json
================================================
{
"name": "jsproxy-client",
"version": "0.0.1",
"description": "",
"main": "boot.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "EtherDream",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"eslint": "^5.16.0"
}
}
================================================
FILE: src/proxy/README.md
================================================
浏览器脚本的源文件
# src
源文件目录
# debug.sh
开发模式。
# release.sh
发布脚本。
================================================
FILE: src/proxy/debug.sh
================================================
webpack \
--o "../home/assets/bundle.debug.js" \
--mode development -w
================================================
FILE: src/proxy/gen-tld/README.md
================================================
顶级域名后缀生成脚本
# 生成
```bash
node gen
```
# TODO
该列表较大,导致最终 JS 增加上百 KB 的体积。
未考虑 `!` 和 `*.` 开头的域名。
未来考虑将结果保存为单独的文件,JS 运行时动态加载。
================================================
FILE: src/proxy/gen-tld/gen.js
================================================
const https = require('https')
const fs = require('fs')
const {stdout} = process
const SRC_PATH = 'https://publicsuffix.org/list/effective_tld_names.dat'
const DST_PATH = '../src/tld-data.js'
stdout.write('loading')
https.get(SRC_PATH, res => {
let str = ''
res.on('data', s => {
str += s
stdout.write('.');
}).on('end', _ => {
proc(str)
}).setEncoding('utf8')
})
function proc(str) {
const list = str
.split('\n')
.filter(v => v)
.filter(v => !v.startsWith('//'))
.filter(v => !v.startsWith('!'))
.map(v => v.replace('*.', ''))
.sort((a, b) => a > b ? 1 : -1)
.join(',')
const result = `\
// THIS FILE WAS GENERATED BY './tools/tld/gen.js'
export default '${list}'
`
fs.writeFileSync(DST_PATH, result)
console.log('\nok')
}
================================================
FILE: src/proxy/jsconfig.json
================================================
{
"compilerOptions": {
"checkJs": true,
"lib": ["webworker", "dom", "es2017"],
"target": "es2017"
},
"include": [
"src/*"
]
}
================================================
FILE: src/proxy/release.sh
================================================
DST=../../www/assets
rm $DST/bundle.*.js
webpack \
--o "$DST/bundle.[hash:8].js" \
--mode production
cd $DST
for i in bundle.*.js; do
printf "\
jsproxy_config=\
x=>{\
__CONF__=x;\
importScripts(__FILE__=x.assets_cdn+'$i')\
};\
importScripts('conf.js')\
" > ../sw.js
done
================================================
FILE: src/proxy/src/cdn.js
================================================
import * as util from './util'
// 暂时先用 jsdelivr 试验。之后换成速度很快、容量更大的免费图床
const CDN = 'https://cdn.jsdelivr.net/npm/jsproxy-cache-01@0.0.'
let mCurVer = -1
/** @type {Map<number, number>} */
let mUrlHashVerMap = new Map()
/** @type {Set<string>} */
let mDirectHostSet = new Set()
async function loadDirectList(conf) {
const url = conf.assets_cdn + conf.direct_host_list
const res = await fetch(url)
const txt = await res.text()
for (const host of txt.split('\n')) {
if (host && host[0] !== '#') {
mDirectHostSet.add(host)
}
}
}
async function loadStaticList(conf) {
const info = conf.static_boost
if (!info || !info.enable) {
return
}
const latest = info.ver
if (mCurVer >= latest) {
return
}
mCurVer = latest
console.log('[jsproxy] cdn cache ver:', latest)
const res = await fetch(CDN + latest + '/full')
const buf = await res.arrayBuffer()
const u32 = new Uint32Array(buf)
let p = 0
for (let ver = 0; ver <= latest; ver++) {
const num = u32[p++]
for (let i = 0; i < num; i++) {
const urlHash = u32[p++]
mUrlHashVerMap.set(urlHash, ver)
}
}
}
export function setConf(conf) {
return Promise.all([
loadStaticList(conf),
loadDirectList(conf),
])
}
/**
* @param {string} host
*/
export function isDirectHost(host) {
return mDirectHostSet.has(host)
}
/**
* @param {string} url
*/
export async function proxyDirect(url) {
try {
const res = await fetch(url, {
referrerPolicy: 'no-referrer',
})
const {status} = res
if (status === 200 || status === 206) {
return res
}
console.warn('direct status:', status, url)
} catch (err) {
console.warn('direct fail:', url)
}
}
/**
* @param {number} urlHash
*/
export function getFileVer(urlHash) {
return mUrlHashVerMap.get(urlHash)
}
/**
* @param {number} urlHash
* @param {number} urlVer
*/
async function proxyStaticMain(urlHash, urlVer) {
const hashHex = util.numToHex(urlHash, 8)
const res = await fetch(CDN + urlVer + '/' + hashHex + '.txt')
if (res.status !== 200) {
throw 'bad status: ' + res.status
}
const buf = await res.arrayBuffer()
const b = new Uint8Array(buf)
const hdrLen = b[0] << 8 | b[1]
const hdrBuf = b.subarray(2, 2 + hdrLen)
const hdrStr = util.bytesToStr(hdrBuf)
const hdrObj = JSON.parse(hdrStr)
const body = b.subarray(2 + hdrLen)
hdrObj['date'] = new Date().toUTCString()
return new Response(body, {
headers: hdrObj
})
}
/**
* @param {number} urlHash
* @param {number} urlVer
*/
export async function proxyStatic(urlHash, urlVer) {
// TODO: 使用多个 CDN
try {
return await proxyStaticMain(urlHash, urlVer)
} catch(err) {
console.warn('cdn fail:', err)
}
}
================================================
FILE: src/proxy/src/client.js
================================================
import * as urlx from './urlx.js'
import * as route from './route.js'
import * as env from './env.js'
import * as hook from './hook.js'
import {createFakeLoc} from './fakeloc.js'
import {createStorage} from './storage.js'
const {
apply,
construct,
} = Reflect
/**
* Hook 页面和 Worker 相同的 API
*
* @param {Window} global WindowOrWorkerGlobalScope
* @param {string} origin
*/
export function init(global, origin) {
// lockNative(win)
// hook Storage API
createStorage(global, origin)
// hook Location API
const fakeLoc = createFakeLoc(global)
// hook Performance API
const perfProto = global['PerformanceEntry'].prototype
hook.prop(perfProto, 'name',
getter => function() {
const val = getter.call(this)
if (/^https?:/.test(val)) {
return urlx.decUrlStrAbs(val)
}
return val
}
)
// hook AJAX API
const xhrProto = global['XMLHttpRequest'].prototype
hook.func(xhrProto, 'open', oldFn => function(_0, url) {
if (url) {
arguments[1] = urlx.encUrlStrRel(url, this)
}
return apply(oldFn, this, arguments)
})
hook.prop(xhrProto, 'responseURL',
getter => function(oldFn) {
const val = getter.call(this)
return urlx.decUrlStrRel(val, this)
}
)
hook.func(global, 'fetch', oldFn => function(v) {
if (v) {
if (v.url) {
// v is Request
const newUrl = urlx.encUrlStrAbs(v.url)
arguments[0] = new Request(newUrl, v)
} else {
// v is string
// TODO: 字符串不传引用,无法获取创建时的 constructor
arguments[0] = urlx.encUrlStrRel(v, v)
}
}
return apply(oldFn, this, arguments)
})
hook.func(global, 'WebSocket', oldFn => function(url) {
const urlObj = urlx.newUrl(url)
if (urlObj) {
const {ori} = env.get(this)
if (ori) {
const args = {
'origin': ori.origin,
}
arguments[0] = route.genWsUrl(urlObj, args)
}
}
return construct(oldFn, arguments)
})
/**
* @param {string} type
*/
function hookWorker(type) {
hook.func(global, type, oldFn => function(url) {
if (url) {
console.log('[jsproxy] new %s: %s', type, url)
arguments[0] = urlx.encUrlStrRel(url, this)
}
return construct(oldFn, arguments)
})
}
hookWorker('Worker')
hookWorker('SharedWorker')
hook.func(global, 'importScripts', oldFn => function(...args) {
const urls = args.map(urlx.encUrlStrRel)
console.log('[jsproxy] importScripts:', urls)
return apply(oldFn, this, urls)
})
}
================================================
FILE: src/proxy/src/cookie.js
================================================
import {Database} from './database.js'
/** @type {Set<Cookie>} */
let mDirtySet = new Set()
function Cookie() {
this.id = ''
this.name = ''
this.value = ''
this.domain = ''
this.hostOnly = false
this.path = ''
this.expires = NaN
this.isExpired = false
this.secure = false
this.httpOnly = false
this.sameSite = ''
}
/**
* @param {Cookie} src
* @param {Cookie} dst
*/
function copy(dst, src) {
dst.id = src.id
dst.name = src.name
dst.value = src.value
dst.domain = src.domain
dst.hostOnly = src.hostOnly
dst.path = src.path
dst.expires = src.expires
dst.isExpired = src.isExpired
dst.secure = src.secure
dst.httpOnly = src.httpOnly
dst.sameSite = src.sameSite
}
/**
* @param {string} cookiePath
* @param {string} urlPath
*/
function isSubPath(cookiePath, urlPath) {
if (urlPath === cookiePath) {
return true
}
if (!cookiePath.endsWith('/')) {
cookiePath += '/'
}
return urlPath.startsWith(cookiePath)
}
/**
* @param {string} cookieDomain
* @param {string} urlDomain
*/
function isSubDomain(cookieDomain, urlDomain) {
return urlDomain === cookieDomain ||
urlDomain.endsWith('.' + cookieDomain)
}
/**
* @param {Cookie} item
* @param {number} now
*/
function isExpire(item, now) {
const v = item.expires
return !isNaN(v) && v < now
}
class CookieDomainNode {
constructor() {
/** @type {Cookie[]} */
this.items = null
/** @type {Object<string, CookieDomainNode>} */
this.children = {}
}
/**
* @param {string} name
*/
nextChild(name) {
return this.children[name] || (
this.children[name] = new CookieDomainNode
)
}
/**
* @param {string} name
*/
getChild(name) {
return this.children[name]
}
/**
* @param {Cookie} cookie
*/
addCookie(cookie) {
if (this.items) {
this.items.push(cookie)
} else {
this.items = [cookie]
}
}
}
/** @type {Map<string, Cookie>} */
const mIdCookieMap = new Map()
const mCookieNodeRoot = new CookieDomainNode()
export function getNonHttpOnlyItems() {
const ret = []
for (const item of mIdCookieMap.values()) {
if (!item.httpOnly) {
ret.push(item)
}
}
return ret
}
/**
* @param {string} str
* @param {URL} urlObj
* @param {number} now
*/
export function parse(str, urlObj, now) {
const item = new Cookie()
const arr = str.split(';')
for (let i = 0; i < arr.length; i++) {
let key, val
const s = arr[i].trim()
const p = s.indexOf('=')
if (p !== -1) {
key = s.substr(0, p)
val = s.substr(p + 1)
} else {
//
// cookie = 's; secure; httponly'
// 0: { key: '', val: 's' }
// 1: { key: 'secure', val: '' }
// 2: { key: 'httponly', val: '' }
//
key = (i === 0) ? '' : s
val = (i === 0) ? s : ''
}
if (i === 0) {
item.name = key
item.value = val
continue
}
switch (key.toLocaleLowerCase()) {
case 'expires':
if (isNaN(item.expires)) {
item.expires = Date.parse(val)
}
break
case 'domain':
if (val[0] === '.') {
val = val.substr(1)
}
item.domain = val
break
case 'path':
item.path = val
break
case 'httponly':
item.httpOnly = true
break
case 'secure':
item.secure = true
break
case 'max-age':
item.expires = now + (+val) * 1000
break
case 'samesite':
item.sameSite = val
break
}
}
if (isExpire(item, now)) {
item.isExpired = true
}
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
if (item.name.startsWith('__Secure-')) {
if (!(
urlObj.protocol === 'https:' &&
item.secure
)) {
return
}
}
if (item.name.startsWith('__Host-')) {
if (!(
urlObj.protocol === 'https:' &&
item.secure &&
item.domain === '' &&
item.path === '/'
)) {
return
}
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Compatibility_notes
if (item.secure && urlObj.protocol === 'http:') {
return
}
// check hostname
const domain = urlObj.hostname
if (item.domain) {
if (!isSubDomain(item.domain, domain)) {
console.warn('[jsproxy] invalid cookie domain! `%s` ⊄ `%s`',
item.domain, domain)
return
}
} else {
item.domain = domain
item.hostOnly = true
}
// check pathname
const path = urlObj.pathname
if (item.path) {
if (!isSubPath(item.path, path)) {
console.warn('[jsproxy] invalid cookie path! `%s` ⊄ `%s`',
item.path, path)
return
}
} else {
item.path = path
}
item.id = (item.secure ? ';' : '') +
item.name + ';' +
item.domain +
item.path
return item
}
/**
* @param {Cookie} item
*/
export function set(item) {
// console.log('set:', item)
const id = item.id
const matched = mIdCookieMap.get(id)
if (matched) {
if (item.isExpired) {
// delete
mIdCookieMap.delete(id)
matched.isExpired = true
// TODO: remove node
} else {
// update
copy(matched, item)
}
mDirtySet.add(matched)
} else {
// create
const labels = item.domain.split('.')
let labelPos = labels.length
let node = mCookieNodeRoot
do {
node = node.nextChild(labels[--labelPos])
} while (labelPos !== 0)
node.addCookie(item)
mIdCookieMap.set(id, item)
mDirtySet.add(item)
}
}
/**
* @param {URL} urlObj
*/
export function query(urlObj) {
const ret = []
const now = Date.now()
const domain = urlObj.hostname
const path = urlObj.pathname
const isHttps = (urlObj.protocol === 'https:')
const labels = domain.split('.')
let labelPos = labels.length
let node = mCookieNodeRoot
do {
node = node.getChild(labels[--labelPos])
if (!node) {
break
}
const items = node.items
if (!items) {
continue
}
for (let i = 0; i < items.length; i++) {
const item = items[i]
// https url | secure flag | carry
// ✔ | ✔ | ✔
// ✔ | ✘ | ✔
// ✘ | ✘ | ✔
// ✘ | ✔ | ✘
if (!isHttps && item.secure) {
continue
}
// HostOnly Cookie 需匹配完整域名
if (item.hostOnly && labelPos !== 0) {
continue
}
if (!isSubPath(item.path, path)) {
continue
}
if (item.isExpired) {
continue
}
if (isExpire(item, now)) {
item.isExpired = true
continue
}
// TODO: same site
let str = item.value
if (item.name) {
str = item.name + '=' + str
}
ret.push(str)
}
} while (labelPos !== 0)
return ret.join('; ')
}
/** @type {Database} */
let mDB
export async function setDB(db) {
mDB = db
const now = Date.now()
await mDB.enum('cookie', v => {
if (isExpire(v, now)) {
mDB.delete('cookie', v.id)
} else {
set(v)
}
return true
})
setInterval(save, 1000 * 3)
}
export async function save() {
if (mDirtySet.size === 0) {
return
}
const tmp = mDirtySet
mDirtySet = new Set()
for (const item of tmp) {
if (item.isExpired) {
await mDB.delete('cookie', item.id)
} else if (!isNaN(item.expires)) {
// 不保存 session cookie
await mDB.put('cookie', item)
}
}
}
================================================
FILE: src/proxy/src/database.js
================================================
import {Signal} from './signal.js'
export class Database {
/**
* @param {string} name
*/
constructor(name) {
this._name = name
/** @type {IDBDatabase} */
this._db = null
}
/**
* @param {string} table
* @param {IDBTransactionMode} mode
*/
_getStore(table, mode) {
return this._db
.transaction(table, mode)
.objectStore(table)
}
/**
* @param {Object<string, IDBObjectStoreParameters>} opts
*/
open(opts) {
const s = new Signal()
const req = indexedDB.open(this._name)
req.onsuccess = (e) => {
const idb = req.result
this._db = idb
idb.onclose = (e) => {
console.warn('[jsproxy] indexedDB disconnected, reopen...')
this.open(opts)
}
s.notify()
}
req.onerror = (e) => {
console.warn('req.onerror:', e)
s.abort(req.error)
}
req.onupgradeneeded = (e) => {
const idb = req.result
for (const [k, v] of Object.entries(opts)) {
idb.createObjectStore(k, v)
}
}
return s.wait()
}
close() {
this._db.close()
}
/**
* @param {string} table
* @param {any} key
*/
get(table, key) {
const s = new Signal()
const obj = this._getStore(table, 'readonly')
const req = obj.get(key)
req.onsuccess = (e) => {
s.notify(req.result)
}
req.onerror = (e) => {
s.abort(req.error)
}
return s.wait()
}
/**
* @param {string} table
* @param {any} record
*/
put(table, record) {
const s = new Signal()
const obj = this._getStore(table, 'readwrite')
const req = obj.put(record)
req.onsuccess = (e) => {
s.notify()
}
req.onerror = (e) => {
s.abort(req.error)
}
return s.wait()
}
/**
* @param {string} table
* @param {any} key
*/
delete(table, key) {
const s = new Signal()
const obj = this._getStore(table, 'readwrite')
const req = obj.delete(key)
req.onsuccess = (e) => {
s.notify()
}
req.onerror = (e) => {
s.abort(req.error)
}
return s.wait()
}
/**
* @param {string} table
* @param {(any) => boolean} callback
*/
enum(table, callback, ...args) {
const s = new Signal()
const obj = this._getStore(table, 'readonly')
const req = obj.openCursor(...args)
req.onsuccess = (e) => {
const {result} = req
if (result) {
if (callback(result.value) !== false) {
result.continue()
}
} else {
s.notify()
}
}
req.onerror = (e) => {
s.abort(req.error)
}
return s.wait()
}
}
================================================
FILE: src/proxy/src/env.js
================================================
export const ENV_PAGE = 1
export const ENV_WORKER = 2
export const ENV_SW = 3
let mEnvType = 0
export function setEnvType(v) {
mEnvType = v
}
export function isSwEnv() {
return mEnvType === ENV_SW
}
export function isWorkerEnv() {
return mEnvType === ENV_WORKER
}
/**
* @type {WeakMap<Object, {loc: Location, doc: Document, ori: URL, domHook: object}>}
*/
const objInfoMap = new WeakMap()
export function add(win, info) {
objInfoMap.set(win.Function, info)
}
export function get(obj) {
const Function = obj.constructor.constructor
return objInfoMap.get(Function)
}
================================================
FILE: src/proxy/src/fakeloc.js
================================================
import * as urlx from "./urlx";
const {
defineProperty,
setPrototypeOf,
} = Object
function setup(obj, fakeLoc) {
defineProperty(obj, '__location', {
get() {
return fakeLoc
},
set(val) {
console.log('[jsproxy] %s set location: %s', obj, val)
fakeLoc.href = val
}
})
}
/**
* @param {Window} global WindowOrWorkerGlobalScope
*/
export function createFakeLoc(global) {
const location = global.location
let ancestorOrigins
/**
* @param {Location | URL} loc
*/
function getPageUrlObj(loc) {
return new URL(urlx.decUrlObj(loc))
}
// 不缓存 location 属性,因为 beforeunload 事件会影响赋值
const locObj = {
get href() {
// console.log('[jsproxy] get location.href')
return getPageUrlObj(location).href
},
// TODO: 精简合并
get protocol() {
return getPageUrlObj(location).protocol
},
get host() {
return getPageUrlObj(location).host
},
get hostname() {
return getPageUrlObj(location).hostname
},
get port() {
return getPageUrlObj(location).port
},
get pathname() {
return getPageUrlObj(location).pathname
},
get search() {
return getPageUrlObj(location).search
},
get hash() {
return getPageUrlObj(location).hash
},
get origin() {
return getPageUrlObj(location).origin
},
toString() {
return this.href
},
toLocaleString() {
return this.href
},
// TODO: Worker 中没有以下属性
get ancestorOrigins() {
if (!ancestorOrigins) {
// TODO: DOMStringList[]
ancestorOrigins = []
let p = global
while ((p = p.parent) !== top) {
const u = getPageUrlObj(p.location)
ancestorOrigins.unshift(u.origin)
}
}
return ancestorOrigins
},
set href(val) {
console.log('[jsproxy] set location.href:', val)
location.href = urlx.encUrlStrRel(val, this)
},
set protocol(val) {
console.log('[jsproxy] set location.protocol:', val)
const urlObj = getPageUrlObj(location)
urlObj.href = val
location.href = urlx.encUrlObj(urlObj)
},
set host(val) {
console.log('[jsproxy] set location.host:', val)
const urlObj = getPageUrlObj(location)
urlObj.host = val
location.href = urlx.encUrlObj(urlObj)
},
set hostname(val) {
console.log('[jsproxy] set location.hostname:', val)
const urlObj = getPageUrlObj(location)
urlObj.hostname = val
location.href = urlx.encUrlObj(urlObj)
},
set port(val) {
console.log('[jsproxy] set location.port:', val)
const urlObj = getPageUrlObj(location)
urlObj.port = val
location.href = urlx.encUrlObj(urlObj)
},
set pathname(val) {
console.log('[jsproxy] set location.pathname:', val)
const urlObj = getPageUrlObj(location)
urlObj.pathname = val
location.href = urlx.encUrlObj(urlObj)
},
set search(val) {
location.search = val
},
set hash(val) {
location.hash = val
},
reload() {
console.warn('[jsproxy] location.reload')
// @ts-ignore
return location.reload(...arguments)
},
replace(val) {
if (val) {
console.warn('[jsproxy] location.replace:', val)
arguments[0] = urlx.encUrlStrRel(val, this)
}
// @ts-ignore
return location.replace(...arguments)
},
assign(val) {
if (val) {
console.warn('[jsproxy] location.assign:', val)
arguments[0] = urlx.encUrlStrRel(val, this)
}
// @ts-ignore
return location.assign(...arguments)
},
}
const locProto = location.constructor.prototype
const fakeLoc = setPrototypeOf(locObj, locProto)
setup(global, fakeLoc)
// 非 Worker 环境
const Document = global['Document']
if (Document) {
// TODO: document.hasOwnProperty('location') 原本是 true
setup(Document.prototype, fakeLoc)
}
return fakeLoc
}
================================================
FILE: src/proxy/src/hook.js
================================================
const {
getOwnPropertyDescriptor,
defineProperty,
apply,
} = Reflect
export const DROP = {}
/**
* hook function
*
* @param {object} obj
* @param {string} key
* @param {(oldFn: Function) => Function} factory
*/
export function func(obj, key, factory) {
/** @type {Function} */
const oldFn = obj[key]
if (!oldFn) {
return false
}
const newFn = factory(oldFn)
Object.keys(oldFn).forEach(k => {
newFn[k] = oldFn[k]
})
const proto = oldFn.prototype
if (proto) {
newFn.prototype = proto
}
obj[key] = newFn
return true
}
/**
* hook property
*
* @param {object} obj
* @param {string} key
* @param {(oldFn: () => any) => Function=} g
* @param {(oldFn: () => void) => Function=} s
*/
export function prop(obj, key, g, s) {
const desc = getOwnPropertyDescriptor(obj, key)
if (!desc) {
return false
}
if (g) {
func(desc, 'get', g)
}
if (s) {
func(desc, 'set', s)
}
defineProperty(obj, key, desc)
return true
}
/**
* @param {Window} win WindowOrWorkerGlobalScope
*/
export function createDomHook(win) {
/**
* @param {object} proto
* @param {string} name
* @param {Function} onget
* @param {Function} onset
*/
function hookElemProp(proto, name, onget, onset) {
prop(proto, name,
getter => function() {
const val = getter.call(this)
return onget.call(this, val)
},
setter => function(val) {
val = onset.call(this, val)
if (val === DROP) {
return
}
setter.call(this, val)
}
)
}
const elemProto = win['Element'].prototype
const rawGetAttr = elemProto.getAttribute
const rawSetAttr = elemProto.setAttribute
const tagAttrHandlersMap = {}
const tagTextHandlerMap = {}
const tagKeySetMap = {}
const tagKeyGetMap = {}
/**
* @param {string} tag
* @param {object} proto
* @param {...any} handlers
*/
function attr(tag, proto, ...handlers) {
/** @type {boolean} */
let hasBind
/** @type {boolean} */
let hasAttr
let keySetMap
let keyGetMap
// TODO: 未考虑上下文
handlers.forEach(v => {
// 带划线的 attr 属性名,转换成驼峰形式的 prop 属性名。
// 例如 `http-equiv` -> `httpEquiv`
const prop = v.name.replace(/-(\w)/g,
(_, char) => char.toUpperCase()
)
hookElemProp(proto, prop, v.onget, v.onset)
// #text
if (prop === 'innerText') {
tagTextHandlerMap[tag] = v
return
}
// attribute
if (tagAttrHandlersMap[tag]) {
tagAttrHandlersMap[tag].push(v)
hasBind = true
} else {
tagAttrHandlersMap[tag] = [v]
tagKeySetMap[tag] = {}
tagKeyGetMap[tag] = {}
}
if (!keySetMap) {
keySetMap = tagKeySetMap[tag]
keyGetMap = tagKeyGetMap[tag]
}
const key = v.name.toLocaleLowerCase()
keySetMap[key] = v.onset
keyGetMap[key] = v.onget
hasAttr = true
})
if (hasBind || !hasAttr) {
return
}
// 如果之前调用过 setAttribute,直接返回上次设置的值;
// 如果没有调用过,则返回 onget 的回调值。
func(proto, 'getAttribute', oldFn => function(name) {
const key = (name + '').toLocaleLowerCase()
const onget = keyGetMap[key]
if (!onget) {
return apply(oldFn, this, arguments)
}
const lastVal = this['_k' + key]
if (lastVal !== undefined) {
return lastVal
}
const val = apply(oldFn, this, arguments)
return onget.call(this, val)
})
func(proto, 'setAttribute', oldFn => function(name, val) {
const key = (name + '').toLocaleLowerCase()
const onset = keySetMap[key]
if (onset) {
this['_k' + key] = val
const ret = onset.call(this, val)
if (ret === DROP) {
return
}
arguments[1] = ret
}
return apply(oldFn, this, arguments)
})
func(proto, 'setAttributeNode', oldFn => function(node) {
console.warn('setAttributeNode:', node, this)
// TODO:
return apply(oldFn, this, arguments)
})
// ...
}
/**
* @param {Node} node
* @param {object} handler
* @param {Element} elem
*/
function parseNewTextNode(node, handler, elem) {
// console.log('parseTextNode')
const val = node.nodeValue
const ret = handler.onset.call(elem, val, true)
if (ret === DROP) {
return
}
node.nodeValue = ret
}
/**
* @param {Element} elem
* @param {object} handler
*/
function parseNewElemNode(elem, handler) {
const name = handler.name
if (!elem.hasAttribute(name)) {
return
}
const val = rawGetAttr.call(elem, name)
const ret = handler.onset.call(elem, val, true)
if (ret === DROP) {
return
}
rawSetAttr.call(elem, name, ret)
}
/**
* @param {Node} node
*/
function addNode(node) {
const type = node.nodeType
if (type === 1) {
/** @type {Element} */
// @ts-ignore
const elem = node
const handlers = tagAttrHandlersMap[elem.tagName]
handlers && handlers.forEach(v => {
parseNewElemNode(elem, v)
})
}
else if (type === 3) {
// TEXT_NODE
const parent = node.parentElement
if (parent) {
const handler = tagTextHandlerMap[parent.tagName]
if (handler) {
parseNewTextNode(node, handler, parent)
}
}
}
}
/**
* @param {Node} node
*/
function delNode(node) {
// TODO: 增加节点删除后的回调
}
return {
attr,
addNode,
delNode,
}
}
================================================
FILE: src/proxy/src/index.js
================================================
import * as env from "./env";
function pageEnv(win) {
env.setEnvType(env.ENV_PAGE)
if (win === top) {
// 开放一个接口,可供 iframe 调用
win.__init__ = function(win) {
page.init(win)
console.log('[jsproxy] child page inited.', win.location.href)
}
// 用于记录 postMessage 发起者的 win
let lastSrcWin
win.__set_srcWin = function(obj) {
lastSrcWin = obj || win
return []
}
win.__get_srcWin = function() {
const ret = lastSrcWin
lastSrcWin = null
return ret
}
// eslint-disable-next-line no-undef
const page = require('./page.js')
page.init(win)
console.log('[jsproxy] top page inited')
} else {
// 子页面直接调用 top 提供的接口,无需重复初始化
top['__init__'](win)
win.__set_srcWin = function() {
return top['__set_srcWin'](win)
}
}
}
function swEnv() {
env.setEnvType(env.ENV_SW)
// eslint-disable-next-line no-undef
require('./sw.js')
}
function workerEnv(global) {
env.setEnvType(env.ENV_WORKER)
// eslint-disable-next-line no-undef
require('./client.js').init(global, location.origin)
global.__set_srcWin = function() {
return []
}
}
function main(global) {
if ('onclick' in global) {
pageEnv(global)
} else if ('onfetch' in global) {
swEnv()
} else {
workerEnv(global)
}
}
main(self)
================================================
FILE: src/proxy/src/inject.js
================================================
import * as path from './path.js'
import * as util from "./util"
// import * as jsfilter from './jsfilter.js'
let mConf
const WORKER_INJECT = util.strToBytes(`\
if (typeof importScripts === 'function' && !self.window && !self.__PATH__) {
self.__PATH__ = '${path.ROOT}';
importScripts('${path.HELPER}');
}
`)
export function getWorkerCode() {
return WORKER_INJECT
}
export function setConf(conf) {
mConf = conf
}
const PADDING = ' '.repeat(500)
const CSP = `\
'self' \
'unsafe-inline' \
file: \
data: \
blob: \
mediastream: \
filesystem: \
chrome-extension-resource: \
`
/**
* @param {URL} urlObj
* @param {number} pageId
*/
export function getHtmlCode(urlObj, pageId) {
const icoUrl = path.PREFIX + urlObj.origin + '/favicon.ico'
const custom = mConf.inject_html || ''
return util.strToBytes(`\
<!-- JS PROXY HELPER -->
<!doctype html>
<link rel="icon" href="${icoUrl}" type="image/x-icon">
<meta http-equiv="content-security-policy" content="frame-src ${CSP}; object-src ${CSP}">
<base href="${urlObj.href}">
<script data-id="${pageId}" src="${path.HELPER}"></script>
${custom}
<!-- PADDING ${PADDING} -->
`)
}
================================================
FILE: src/proxy/src/jsfilter.js
================================================
import * as util from './util.js'
/**
* @param {string} code
*/
export function parseStr(code) {
// TODO: parse js ast
let match = false
code = code.replace(/(\b)location(\b)/g, (_, $1, $2) => {
match = true
return $1 + '__location' + $2
})
code = code.replace(/postMessage\s*\(/g, s => {
match = true
return s + `...(self.__set_srcWin?__set_srcWin():[]), `
})
if (match) {
return code
}
return null
}
/**
* @param {Uint8Array} buf
* @param {string} charset
*/
export function parseBin(buf, charset) {
const str = util.bytesToStr(buf, charset)
const ret = parseStr(str)
if (ret !== null) {
return util.strToBytes(ret)
}
if (charset && !util.isUtf8(charset)) {
return util.strToBytes(str)
}
return null
}
================================================
FILE: src/proxy/src/msg.js
================================================
export const PAGE_INFO_PULL = 1
export const SW_INFO_PUSH = 2
export const PAGE_COOKIE_PUSH = 3
export const SW_COOKIE_PUSH = 4
export const PAGE_INIT_BEG = 5
export const PAGE_INIT_END = 6
export const PAGE_CONF_SET = 110
export const PAGE_CONF_GET = 111
export const PAGE_RELOAD_CONF = 112
export const SW_CONF_RETURN = 112
export const SW_CONF_CHANGE = 113
export const PAGE_READY_CHECK = 200
export const SW_READY = 201
================================================
FILE: src/proxy/src/network.js
================================================
import * as route from './route.js'
import * as cookie from './cookie.js'
import * as urlx from './urlx.js'
import * as util from './util'
import * as tld from './tld.js'
import * as cdn from './cdn.js'
import {Database} from './database.js'
const REFER_ORIGIN = location.origin + '/'
const ENABLE_3RD_COOKIE = true
/** @type {Database} */
let mDB
// 部分浏览器不支持 access-control-expose-headers: *
// https://developer.mz.jsproxy.tk/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers#Compatibility_notes
//
// 如果返回所有字段名,长度会很大。
// 因此请求头中设置 aceh__ 标记,告知服务器是否要返回所有字段名。
let mIsAcehOld = true
// TODO:
let mConf
export function setConf(conf) {
mConf = conf
cdn.setConf(conf)
}
export async function setDB(db) {
mDB = db
// clear expires
}
/**
* @param {string} url
*/
function getUrlCache(url) {
return mDB.get('url-cache', url)
}
/**
* @param {string} url
* @param {string} host
* @param {string} info
* @param {number} expires
*/
async function setUrlCache(url, host, info, expires) {
await mDB.put('url-cache', {url, host, info, expires})
}
/**
* @param {string} url
*/
async function delUrlCache(url) {
await mDB.delete('url-cache', url)
}
/**
* @param {URL} targetUrlObj
* @param {URL} clientUrlObj
* @param {Request} req
*/
function getReqCookie(targetUrlObj, clientUrlObj, req) {
const cred = req.credentials
if (cred === 'omit') {
return ''
}
if (cred === 'same-origin') {
// TODO:
const targetTld = tld.getTld(targetUrlObj.hostname)
const clientTld = tld.getTld(clientUrlObj.hostname)
if (targetTld !== clientTld) {
return ''
}
}
return cookie.query(targetUrlObj)
}
/**
* @param {Headers} header
*/
function parseResCache(header) {
const cacheStr = header.get('cache-control')
if (cacheStr) {
if (/no-cache/i.test(cacheStr)) {
return -1
}
const m = cacheStr.match(/(?:^|,\s*)max-age=["]?(\d+)/i)
if (m) {
const sec = +m[1]
if (sec > 0) {
return sec
}
}
}
const expires = header.get('expires')
if (expires) {
const ts = Date.parse(expires)
if (ts > 0) {
return (ts - Date.now()) / 1000 | 0
}
}
return 0
}
/**
* @param {string[]} cookieStrArr
* @param {URL} urlObj
* @param {URL} cliUrlObj
*/
function procResCookie(cookieStrArr, urlObj, cliUrlObj) {
if (!ENABLE_3RD_COOKIE) {
const urlTld = tld.getTld(urlObj.hostname)
const cliTld = tld.getTld(cliUrlObj.hostname)
if (cliTld !== urlTld) {
return
}
}
const ret = []
const now = Date.now()
for (const str of cookieStrArr) {
const item = cookie.parse(str, urlObj, now)
if (!item) {
continue
}
cookie.set(item)
if (!item.httpOnly) {
ret.push(item)
}
}
return ret
}
/**
* @param {Response} res
*/
function getResInfo(res) {
const rawHeaders = res.headers
let status = res.status
/** @type {string[]} */
const cookieStrArr = []
const headers = new Headers()
rawHeaders.forEach((val, key) => {
if (key === 'access-control-allow-origin' ||
key === 'access-control-expose-headers') {
return
}
if (key === '--s') {
status = +val
return
}
if (key === '--t') {
return
}
// 还原重名字段
// 0-key: v1
// 1-key: v2
// =>
// key: v1, v2
//
// 对于 set-cookie 单独存储,因为合并会破坏 cookie 格式:
// var h = new Headers()
// h.append('set-cookie', 'hello')
// h.append('set-cookie', 'world')
// h.get('set-cookie') // "hello, world"
//
const m = key.match(/^\d+-(.+)/)
if (m) {
key = m[1]
if (key === 'set-cookie') {
cookieStrArr.push(val)
} else {
headers.append(key, val)
}
return
}
// 还原转义字段(`--key` => `key`)
if (key.startsWith('--')) {
key = key.substr(2)
}
// 单个 set-cookie 返回头
if (key === 'set-cookie') {
cookieStrArr.push(val)
return
}
headers.set(key, val)
})
return {status, headers, cookieStrArr}
}
// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte
const R_UNSAFE_REQ_HDR_CHAR =
// eslint-disable-next-line no-control-regex
/[\x00-\x08\x0a-\x1f\x22\x28\x29\x3a\x3c\x3e\x3f\x40\x5b\x5c\x5d\x7b\x7d\x7f]/
/**
* @param {string} key
* @param {string} val
*/
function isSimpleReqHdr(key, val) {
if (key === 'content-type') {
return (
val === 'application/x-www-form-urlencoded' ||
val === 'multipart/form-data' ||
val === 'text/plain'
)
}
if (key === 'accept' ||
key === 'accept-language' ||
key === 'content-language'
) {
// 标准是总和小于 1024,这里保守一些
return val.length < 256 &&
!R_UNSAFE_REQ_HDR_CHAR.test(val)
}
}
/**
* @param {Request} req
* @param {URL} urlObj
* @param {URL} cliUrlObj
*/
function initReqHdr(req, urlObj, cliUrlObj) {
const reqHdr = new Headers()
const reqMap = {
'--ver': mConf.ver,
'--mode': req.mode,
'--type': req.destination || '',
'origin': '',
}
if (mIsAcehOld) {
reqMap['--aceh'] = '1'
}
req.headers.forEach((val, key) => {
if (key === 'user-agent') {
return
}
if (isSimpleReqHdr(key, val)) {
reqHdr.set(key, val)
} else {
reqMap[key] = val
}
})
if (reqMap['origin']) {
reqMap['origin'] = cliUrlObj.origin
}
const referer = req.referrer
if (referer) {
// TODO: CSS 引用图片的 referer 不是页面 URL,而是 CSS URL
if (referer === REFER_ORIGIN) {
// Referrer Policy: origin
reqMap['referer'] = cliUrlObj.origin + '/'
} else {
reqMap['referer'] = urlx.decUrlStrAbs(referer)
}
}
reqMap['cookie'] = getReqCookie(urlObj, cliUrlObj, req)
return {reqHdr, reqMap}
}
/**
* @param {RequestInit} reqOpt
* @param {Object<string, string>} info
*/
function updateReqHeaders(reqOpt, info) {
reqOpt.referrer = '/?' + new URLSearchParams(info)
}
const MAX_RETRY = 5
/**
* @param {Request} req
* @param {URL} urlObj
* @param {URL} cliUrlObj
*/
export async function launch(req, urlObj, cliUrlObj) {
const {method} = req
/** @type {RequestInit} */
const reqOpt = {
mode: 'cors',
method,
}
if (method === 'POST' && !req.bodyUsed) {
if (req.body) {
reqOpt.body = req.body
} else {
const buf = await req.arrayBuffer()
if (buf.byteLength > 0) {
reqOpt.body = buf
}
}
}
if (req.signal) {
reqOpt.signal = req.signal
}
if (!urlx.isHttpProto(urlObj.protocol)) {
// 非 HTTP 协议的资源,直接访问
// 例如 youtube 引用了 chrome-extension: 协议的脚本
const res = await fetch(req)
return {res}
}
const url = urlObj.href
const urlHash = util.strHash(url)
let host = ''
let rawInfo = ''
const {reqHdr, reqMap} = initReqHdr(req, urlObj, cliUrlObj)
reqOpt.headers = reqHdr
while (method === 'GET') {
// 该资源是否加载过?
const r = await getUrlCache(url)
if (r && r.host) {
const now = util.getTimeSeconds()
if (now < r.expires) {
// 使用之前的节点,提高缓存命中率
host = r.host
rawInfo = r.info
break
}
}
// 支持 CORS 的站点,可直连
if (cdn.isDirectHost(urlObj.host)) {
console.log('direct hit:', url)
const res = await cdn.proxyDirect(url)
if (res) {
setUrlCache(url, '', '', 0)
return {res}
}
}
// 常用静态资源 CDN 加速
const ver = cdn.getFileVer(urlHash)
if (ver >= 0) {
console.log('cdn hit:', url)
const res = await cdn.proxyStatic(urlHash, ver)
if (res) {
setUrlCache(url, '', '', 0)
return {res}
}
}
break
}
// TODO: 此处逻辑需要优化
let level = 1
// 如果缓存未命中产生请求,服务器不做节点切换
if (host) {
level = 0
}
/** @type {Response} */
let res
/** @type {Headers} */
let resHdr
for (let i = 0; i < MAX_RETRY; i++) {
if (i === 0 && host) {
// 使用缓存的主机
} else {
host = route.getHost(urlHash, level)
}
const rawUrl = urlx.delHash(urlObj.href)
let proxyUrl = route.genUrl(host, 'http') + '/' + rawUrl
// 即使未命中缓存,在请求“加速节点”时也能带上文件信息
if (rawInfo) {
reqMap['--raw-info'] = rawInfo
} else {
delete reqMap['--raw-info']
}
res = null
try {
reqMap['--level'] = level
updateReqHeaders(reqOpt, reqMap)
res = await fetch(proxyUrl, reqOpt)
} catch (err) {
console.warn('fetch fail:', proxyUrl)
break
// TODO: 重试其他线路
// route.setFailHost(host)
}
resHdr = res.headers
// 检测浏览器是否支持 aceh: *
if (mIsAcehOld && resHdr.has('--t')) {
mIsAcehOld = false
delete reqMap['--aceh']
}
// 是否切换节点
if (resHdr.has('--switched')) {
rawInfo = resHdr.get('--raw-info')
level++
continue
}
// 目前只有加速节点会返回该信息
const resErr = resHdr.get('--error')
if (resErr) {
console.warn('[jsproxy] cfworker fail:', resErr)
rawInfo = ''
level = 0
continue
}
break
}
if (!res) {
return
}
const {
status, headers, cookieStrArr
} = getResInfo(res)
if (method === 'GET' && status === 200) {
const cacheSec = parseResCache(headers)
if (cacheSec >= 0) {
const expires = util.getTimeSeconds() + cacheSec + 1000
setUrlCache(url, host, rawInfo, expires)
}
}
// 处理 HTTP 返回头的 refresh 字段
// http://www.otsukare.info/2015/03/26/refresh-http-header
const refresh = headers.get('refresh')
if (refresh) {
const newVal = urlx.replaceHttpRefresh(refresh, url)
if (newVal !== refresh) {
console.log('[jsproxy] http refresh:', refresh)
headers.set('refresh', newVal)
}
}
let cookies
if (cookieStrArr.length) {
const items = procResCookie(cookieStrArr, urlObj, cliUrlObj)
if (items.length) {
cookies = items
}
}
return {res, status, headers, cookies}
}
================================================
FILE: src/proxy/src/page.js
================================================
import * as MSG from './msg.js'
import * as route from './route.js'
import * as util from './util.js'
import * as urlx from './urlx.js'
import * as hook from './hook.js'
import * as cookie from './cookie.js'
import * as jsfilter from './jsfilter.js'
import * as env from './env.js'
import * as client from './client.js'
const {
apply,
} = Reflect
function initDoc(win, domHook) {
const document = win.document
const headElem = document.head
const baseElemList = document.getElementsByTagName('base')
const baseElem = baseElemList[0]
document.__baseElem = baseElem
//
// 监控元素创建和删除
//
const nodeSet = new WeakSet()
function onNodeAdd(node) {
if (nodeSet.has(node)) {
return
}
nodeSet.add(node)
const nodes = node.childNodes
for (let i = 0, n = nodes.length; i < n; i++) {
onNodeAdd(nodes[i])
}
domHook.addNode(node)
}
function onNodeDel(node) {
nodeSet.delete(node)
const nodes = node.childNodes
for (let i = 0, n = nodes.length; i < n; i++) {
onNodeDel(nodes[i])
}
domHook.delNode(node)
// TODO: 逻辑优化
if (node === baseElem) {
// 默认的 <base> 元素可能会被删除,需要及时补上
headElem.insertBefore(baseElem, headElem.firstChild)
console.warn('[jsproxy] base elem restored')
}
}
/**
* @param {MutationRecord[]} mutations
*/
function parseMutations(mutations) {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(onNodeAdd)
mutation.removedNodes.forEach(onNodeDel)
})
}
const observer = new win.MutationObserver(parseMutations)
observer.observe(document, {
childList: true,
subtree: true,
})
}
/**
* Hook 页面 API
*
* @param {Window} win
*/
export function init(win) {
if (!win) {
return
}
try {
win['x']
} catch (err) {
// TODO: 不应该出现
console.warn('not same origin')
return
}
const document = win.document
// 该 window 之前已初始化过,现在只需更新 document。
// 例如 iframe 加载完成之前,读取 contentWindow 得到的是空白页,
// 加载完成后,document 对象会变化,但 window 上的属性仍保留。
const info = env.get(win['Math'])
if (info) {
const {doc, domHook} = info
if (doc !== document) {
// 加载完成后,初始化实际页面的 document
initDoc(win, domHook)
info[1] = document
}
return
}
const {
location,
navigator,
} = win
// 源路径(空白页继承上级页面)
const oriUrlObj = new URL(document.baseURI)
const domHook = hook.createDomHook(win)
// 关联当前页面上下文信息
env.add(win, {
loc: location,
doc: document,
ori: oriUrlObj,
domHook,
})
// hook 页面和 Worker 相同的 API
client.init(win, oriUrlObj.origin)
// 首次安装 document
// 如果访问加载中的页面,返回 about:blank 空白页
initDoc(win, domHook)
const sw = navigator.serviceWorker
const swCtl = sw.controller
function sendMsgToSw(cmd, val) {
swCtl && swCtl.postMessage([cmd, val])
}
// TODO: 这部分逻辑需要优化
let readyCallback
function pageAsyncInit() {
const curScript = document.currentScript
if (!curScript) {
return
}
// curScript.remove()
const pageId = +curScript.dataset['id']
// console.log('PAGE wait id:', pageId)
if (!pageId) {
console.warn('[jsproxy] missing page id')
return
}
readyCallback = function() {
sendMsgToSw(MSG.PAGE_INIT_END, pageId)
}
sendMsgToSw(MSG.PAGE_INIT_BEG, pageId)
// do async init
// if (win === top) {
sendMsgToSw(MSG.PAGE_INFO_PULL)
// } else {
// readyCallback()
// }
}
pageAsyncInit()
sw.addEventListener('message', e => {
const [cmd, val] = e.data
switch (cmd) {
case MSG.SW_COOKIE_PUSH:
// console.log('PAGE MSG.SW_COOKIE_PUSH:', val)
val.forEach(cookie.set)
break
case MSG.SW_INFO_PUSH:
// console.log('PAGE MSG.SW_INFO_PUSH:', val)
val.cookies.forEach(cookie.set)
route.setConf(val.conf)
readyCallback()
break
case MSG.SW_CONF_CHANGE:
route.setConf(val)
break
}
e.stopImmediatePropagation()
}, true)
sw.startMessages && sw.startMessages()
//
// hook ServiceWorker
//
const swProto = win['ServiceWorkerContainer'].prototype
if (swProto) {
hook.func(swProto, 'register', oldFn => function() {
console.warn('access serviceWorker.register blocked')
return new Promise(function() {})
})
hook.func(swProto, 'getRegistration', oldFn => function() {
console.warn('access serviceWorker.getRegistration blocked')
return new Promise(function() {})
})
hook.func(swProto, 'getRegistrations', oldFn => function() {
console.warn('access serviceWorker.getRegistrations blocked')
return new Promise(function() {})
})
}
/**
* History API
* @param {string} name
*/
function hookHistory(name) {
const proto = win['History'].prototype
hook.func(proto, name, oldFn =>
/**
* @param {*} data
* @param {string} title
* @param {string} url 相对或绝对路径
*/
function(data, title, url) {
console.log('[jsproxy] history.%s: %s', name, url)
const {loc, doc} = env.get(this)
if (doc && url) {
const dstUrlObj = urlx.newUrl(url, doc.baseURI)
if (dstUrlObj) {
// 当前页面 URL
const srcUrlStr = urlx.decUrlObj(loc)
const srcUrlObj = new URL(srcUrlStr)
if (srcUrlObj.origin !== dstUrlObj.origin) {
throw Error(`\
Failed to execute '${name}' on 'History': \
A history state object with URL '${url}' \
cannot be created in a document with \
origin '${srcUrlObj.origin}' and URL '${srcUrlStr}'.`
)
}
arguments[2] = urlx.encUrlObj(dstUrlObj)
}
}
return apply(oldFn, this, arguments)
})
}
hookHistory('pushState')
hookHistory('replaceState')
//
// hook window.open()
//
hook.func(win, 'open', oldFn => function(url) {
if (url) {
arguments[0] = urlx.encUrlStrRel(url, url)
}
return apply(oldFn, this, arguments)
})
//
// hook window.frames[...]
//
const frames = win.frames
// @ts-ignore
win.frames = new Proxy(frames, {
get(_, key) {
if (typeof key === 'number') {
console.log('get frames index:', key)
const win = frames[key]
init(win)
return win
} else {
return frames[key]
}
}
})
//
hook.func(navigator, 'registerProtocolHandler', oldFn => function(_0, url, _1) {
console.log('registerProtocolHandler:', arguments)
return apply(oldFn, this, arguments)
})
//
// hook document.domain
//
const docProto = win['Document'].prototype
let domain = oriUrlObj.hostname
hook.prop(docProto, 'domain',
getter => function() {
return domain
},
setter => function(val) {
console.log('[jsproxy] set document.domain:', val)
domain = val
// TODO:
setter.call(this, location.hostname)
}
)
//
// hook document.cookie
//
hook.prop(docProto, 'cookie',
getter => function() {
// console.log('[jsproxy] get document.cookie')
const {ori} = env.get(this)
return cookie.query(ori)
},
setter => function(val) {
// console.log('[jsproxy] set document.cookie:', val)
const {ori} = env.get(this)
const item = cookie.parse(val, ori, Date.now())
if (item) {
cookie.set(item)
sendMsgToSw(MSG.PAGE_COOKIE_PUSH, item)
}
}
)
// hook uri api
function getUriHook(getter) {
return function() {
const val = getter.call(this)
return val && urlx.decUrlStrAbs(val)
}
}
hook.prop(docProto, 'referrer', getUriHook)
hook.prop(docProto, 'URL', getUriHook)
hook.prop(docProto, 'documentURI', getUriHook)
const nodeProto = win['Node'].prototype
hook.prop(nodeProto, 'baseURI', getUriHook)
// hook Message API
const msgEventProto = win['MessageEvent'].prototype
hook.prop(msgEventProto, 'origin',
getter => function() {
const {ori} = env.get(this)
return ori.origin
}
)
hook.func(win, 'postMessage', oldFn => function(msg, origin) {
const srcWin = top['__get_srcWin']() || this
// console.log(srcWin)
if (origin && origin !== '*') {
arguments[1] = '*'
}
return apply(oldFn, srcWin, arguments)
})
//
// hook <meta>
//
const metaProto = win['HTMLMetaElement'].prototype
domHook.attr('META', metaProto,
{
name: 'http-equiv',
onget(val) {
// TODO:
return val
},
onset(val) {
let newVal
switch (val.toLowerCase()) {
case 'refresh':
newVal = urlx.replaceHttpRefresh(this.content, this)
if (newVal !== val) {
console.warn('[jsproxy] meta redir')
this.content = newVal
}
break
case 'content-security-policy':
console.warn('[jsproxy] meta csp removed')
this.remove()
break
case 'content-type':
this.remove()
break
}
return val
}
},
{
name: 'charset',
onget(val) {
return val
},
onset(val) {
return 'utf-8'
}
}
)
//
// hook 元素的 URL 属性,JS 读取时伪装成原始值
//
function hookAttr(tag, proto, name) {
domHook.attr(tag, proto, {
name,
onget(val) {
if (val === null) {
return ''
}
return urlx.decUrlStrRel(val, this)
},
onset(val) {
if (val === '') {
return val
}
return urlx.encUrlStrRel(val, this)
}
})
}
const anchorProto = win['HTMLAnchorElement'].prototype
hookAttr('A', anchorProto, 'href')
const areaProto = win['HTMLAreaElement'].prototype
hookAttr('AREA', areaProto, 'href')
const formProto = win['HTMLFormElement'].prototype
hookAttr('FORM', formProto, 'action')
const scriptProto = win['HTMLScriptElement'].prototype
const linkProto = win['HTMLLinkElement'].prototype
// 防止混合内容
if (oriUrlObj.protocol === 'http:') {
hookAttr('SCRIPT', scriptProto, 'src')
hookAttr('LINK', linkProto, 'href')
}
// const imgProto = win.HTMLImageElement.prototype
// hookAttr('IMG', imgProto, 'src')
const embedProto = win['HTMLEmbedElement'].prototype
hookAttr('EMBED', embedProto, 'src')
const objectProto = win['HTMLObjectElement'].prototype
hookAttr('OBJECT', objectProto, 'data')
const iframeProto = win['HTMLIFrameElement'].prototype
hookAttr('IFRAME', iframeProto, 'src')
const frameProto = win['HTMLFrameElement'].prototype
hookAttr('FRAME', frameProto, 'src')
// 更新默认的 baseURI
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#Usage_notes
const baseProto = win['HTMLBaseElement'].prototype
domHook.attr('BASE', baseProto,
{
name: 'href',
onget(val) {
return this.__href || val
},
onset(val) {
// TODO: 逻辑优化
const baseElem = this.ownerDocument.__baseElem
if (!baseElem || baseElem === this) {
return val
}
console.log('[jsproxy] baseURI updated:', val)
const urlObj = urlx.newUrl(val, baseElem.href)
baseElem.href = urlObj.href
this.__href = val
return ''
}
})
//
// hook frame
//
hook.prop(iframeProto, 'contentWindow',
getter => function() {
// TODO: origin check
const win = getter.call(this)
init(win)
return win
}
)
hook.prop(iframeProto, 'contentDocument',
getter => function() {
// TODO: origin check
const doc = getter.call(this)
if (doc) {
init(doc.defaultView)
}
return doc
}
)
//
// hook 超链接的 host、pathname 等属性
// 这类属性只有 property 没有 attribute
//
function hookAnchorUrlProp(proto) {
/**
* @param {string} key
*/
function setupProp(key) {
hook.prop(proto, key,
getter => function() {
// 读取 href 时会经过 hook 处理,得到的已是原始 URL
const urlObj = new URL(this.href)
return urlObj[key]
},
setter => function(val) {
// console.log('[jsproxy] set link %s: %s', key, val)
const urlObj = new URL(this.href)
urlObj[key] = val
this.href = urlObj.href
}
)
}
setupProp('protocol')
setupProp('hostname')
setupProp('host')
setupProp('port')
setupProp('pathname')
setupProp('origin')
}
hookAnchorUrlProp(anchorProto)
hookAnchorUrlProp(areaProto)
// 该 form 可能没有经过 MutationObserver 处理
hook.func(formProto, 'submit', oldFn => function() {
this.action = this.action
return apply(oldFn, this, arguments)
})
//
// 监控 离屏元素.click() 方式打开页面
// 例如:
// var s = document.createElement('div')
// s.innerHTML = '<a href="https://google.com"><img></a>'
// s.getElementsByTagName('img')[0].click()
//
const htmlProto = win['HTMLElement'].prototype
hook.func(htmlProto, 'click', oldFn => function() {
/** @type {HTMLAnchorElement} */
let el = this
// 添加到文档时已经过 MutationObserver 处理
// 无需调整 href 属性
if (el.isConnected) {
return
}
while (el) {
const tag = el.tagName
if (tag === 'A' || tag === 'AREA') {
// eslint-disable-next-line no-self-assign
el.href = el.href
break
}
// @ts-ignore
el = el.parentNode
}
return apply(oldFn, this, arguments)
})
//
// 脚本元素处理
//
/** @type {WeakMap<HTMLElement, string>} */
const integrityMap = new WeakMap()
/** @type {WeakMap<HTMLElement, string>} */
const charsetMap = new WeakMap()
domHook.attr('SCRIPT', scriptProto,
// 统一使用 UTF-8 编码
// JS 未提供 UTF-8 转非 UTF-8 的 API,导致编码转换比较麻烦,
// 因此 SW 将所有 JS 资源都转换成 UTF-8 编码。
{
name: 'charset',
onget(val) {
return charsetMap.get(this) || val
},
onset(val) {
if (!util.isUtf8(val)) {
val = 'utf-8'
}
charsetMap.set(this, val)
return val
}
},
// 禁止设置内容校验
//(调整静态 HTML 时控制台会有告警,但不会阻止运行)
{
name: 'integrity',
onget(val) {
return integrityMap.get(this) || ''
},
onset(val) {
integrityMap.set(this, val)
return ''
}
},
// 监控动态创建的脚本
//(设置 innerHTML 时同样会触发)
{
name: 'innerText',
onget(val) {
return val
},
onset(val, isInit) {
const ret = updateScriptText(this, val)
if (ret === null) {
return isInit ? hook.DROP : val
}
return ret
}
})
// text 属性只有 prop 没有 attr
hook.prop(scriptProto, 'text',
getter => function() {
return getter.call(this)
},
setter => function(val) {
const ret = updateScriptText(this, val)
if (ret === null) {
setter.call(this, val)
}
setter.call(this, ret)
}
)
/** @type {WeakSet<HTMLScriptElement>} */
const parsedSet = new WeakSet()
/**
* @param {HTMLScriptElement} elem
*/
function updateScriptText(elem, code) {
// 有些脚本仅用于存储数据(例如模块字符串),无需处理
const type = elem.type
if (type && !util.isJsMime(type)) {
return null
}
if (parsedSet.has(elem)) {
return null
}
parsedSet.add(elem)
return jsfilter.parseStr(code)
}
/**
* 处理 <tag onevent=""> 形式的脚本
* @param {string} eventName
*/
function hookEvent(eventName) {
const scanedSet = new WeakSet()
function scanElement(el) {
if (scanedSet.has(el)) {
return
}
scanedSet.add(el)
// 非元素节点
if (el.nodeType != 1 /*Node.ELEMENT_NODE*/) {
return
}
// 扫描内联代码
if (el[eventName]) {
const code = el.getAttribute(eventName)
if (code) {
const ret = jsfilter.parseStr(code)
if (ret) {
el[eventName] = ret
console.log('[jsproxy] jsfilter onevent:', eventName)
}
}
}
// 扫描上级元素
scanElement(el.parentNode)
}
document.addEventListener(eventName.substr(2), e => {
scanElement(e.target)
}, true)
}
hookEvent('onerror')
hookEvent('onload')
hookEvent('onclick')
// Object.keys(htmlProto).forEach(v => {
// if (v.startsWith('on')) {
// hookEvent(v)
// }
// })
}
================================================
FILE: src/proxy/src/path.js
================================================
export const ROOT = getRootPath()
export const HOME = ROOT + 'index.html'
export const CONF = ROOT + 'conf.js'
export const ICON = ROOT + 'favicon.ico'
export const HELPER = ROOT + '__sys__/helper.js'
export const ASSETS = ROOT + '__sys__/assets/'
export const PREFIX = ROOT + '-----'
function getRootPath() {
//
// 如果运行在代理页面,当前路径:
// https://example.com/path/to/-----url
// 如果运行在 SW,当前路径:
// https://example.com/path/to/sw.js
// 如果运行在 Worker,当前路径:
// __PATH__
// 返回:
// https://example.com/path/to/
//
/** @type {string} */
const envPath = self['__PATH__']
if (envPath) {
return envPath
}
let url = location.href
const pos = url.indexOf('/-----http')
if (pos === -1) {
// sw
url = url.replace(/[^/]+$/, '')
} else {
// page
url = url.substr(0, pos)
}
return url.replace(/\/*$/, '/')
}
================================================
FILE: src/proxy/src/route.js
================================================
import * as urlx from './urlx.js'
import * as util from './util'
let mConf
let mNodeLinesMap
/**
* @param {number} urlHash
* @param {string} id
* @returns {string}
*/
function getHostByNodeId(urlHash, id) {
let a = 0
for (const {weight, host} of mNodeLinesMap[id]) {
if ((a += weight) > urlHash) {
return host
}
}
}
/**
* @param {string} host
*/
function isLocalhost(host) {
return /^(localhost|127\.\d+\.\d+\.\d+)([:/]|$)/.test(host)
}
/**
* @param {string} host
* @param {string} scheme
*/
export function genUrl(host, scheme) {
const s = isLocalhost(host) ? '' : 's'
return `${scheme}${s}://${host}/${scheme}`
}
/**
* @param {number} urlHash
* @param {number} level
*/
export function getHost(urlHash, level) {
let node = mConf.node_default
// 实验中...
if (level === 2) {
node = mConf.node_acc
}
return getHostByNodeId(urlHash, node)
}
// export function setFailHost(host) {
// }
/**
* @param {URL} urlObj
* @param {Object<string, string>} args
*/
export function genWsUrl(urlObj, args) {
let scheme = 'https'
switch (urlObj.protocol) {
case 'wss:':
break
case 'ws:':
scheme = 'http'
break
default:
return null
}
const t = urlx.delScheme(urlx.delHash(urlObj.href))
args['url__'] = scheme + '://' + t
args['ver__'] = mConf.ver
const urlHash = util.strHash(urlObj.href)
const host = getHostByNodeId(urlHash, mConf.node_default)
return genUrl(host, 'ws') + '?' + new URLSearchParams(args)
}
/**
* @param {object} conf
*/
export function setConf(conf) {
mConf = conf
mNodeLinesMap = {}
for (const [id, info] of Object.entries(conf.node_map)) {
const lines = []
let weightSum = 0
for (const [host, weight] of Object.entries(info.lines)) {
weightSum += weight
lines.push({host, weight})
}
// 权重值按比例转换成 0 ~ 2^32 之间的整数,方便后续计算
for (const v of lines) {
v.weight = (v.weight / weightSum * 0xFFFFFFFF) >>> 0
}
lines.sort((a, b) => b.weight - a.weight)
mNodeLinesMap[id] = lines
}
}
================================================
FILE: src/proxy/src/signal.js
================================================
/**
* Promise 简单封装
*
* 封装前
* ```
* function get(...) {
* return new Promise(function(resolve, reject) {
* ...
* function callback(err, result) {
* if (err) {
* reject(err)
* } else {
* resolve(result)
* }
* }
* ...
* }
* }
* ...
* await get(...)
* ```
*
*
* 封装后
* ```
* function get(...) {
* ...
* const s = new Signal()
* function callback(err, result) {
* if (err) {
* s.abort(err)
* } else {
* s.notify(result)
* }
* }
* ...
* return s.wait()
* }
* ...
* await get(...)
* ```
*/
export class Signal {
constructor() {
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve
this._reject = reject
})
}
wait() {
return this._promise
}
notify(arg) {
this._resolve(arg)
}
abort(arg) {
this._reject(arg)
}
}
================================================
FILE: src/proxy/src/storage.js
================================================
import * as hook from './hook.js'
import * as urlx from './urlx.js'
const {
apply,
defineProperty,
ownKeys,
getOwnPropertyDescriptor,
} = Reflect
const undefined = void 0
/**
* @param {WindowOrWorkerGlobalScope} win
* @param {string} name
* @param {string} prefix
*/
function setup(win, name, prefix) {
/** @type {Storage} */
const raw = win[name]
if (!raw) {
return
}
const prefixLen = prefix.length
const nativeMap = {
getItem,
setItem,
removeItem,
clear,
key,
constructor: raw.constructor,
toString: () => raw.toString(),
[Symbol.toStringTag]: 'Storage',
get length() {
return getAllKeys().length
},
}
/**
* @param {*} key
*/
function getItem(key) {
return raw.getItem(prefix + key)
}
/**
* @param {*} key
* @param {string} val
*/
function setItem(key, val) {
// TODO: 同步到 indexedDB
raw.setItem(prefix + key, val)
}
/**
* @param {*} key
*/
function removeItem(key) {
return raw.removeItem(prefix + key)
}
function clear() {
getAllKeys().forEach(removeItem)
}
/**
* @param {*} val
*/
function key(val) {
// TODO: 无需遍历所有
const arr = getAllKeys()
const ret = arr[val | 0]
if (ret === undefined) {
return null
}
return ret
}
/**
* @returns {string[]}
*/
function getAllKeys() {
const ret = []
const keys = ownKeys(raw)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (typeof key !== 'string') {
continue
}
if (!key.startsWith(prefix)) {
continue
}
ret.push(key.substr(prefixLen))
}
return ret
}
const storage = new Proxy(raw, {
get(obj, key) {
const val = nativeMap[key]
if (val !== undefined) {
return val
}
console.log('[jsproxy] %s get: %s', name, key)
const ret = getItem(key)
if (ret === null) {
return undefined
}
return ret
},
set(obj, key, val) {
if (key in nativeMap) {
nativeMap[key] = val
return
}
console.log('[jsproxy] %s set: %s = %s', name, key, val)
setItem(key, val)
return true
},
deleteProperty(obj, key) {
console.log('[jsproxy] %s del: %s', name, key)
removeItem(key)
return true
},
has(obj, key) {
console.log('[jsproxy] %s has: %s', name, key)
if (typeof key === 'string') {
return (prefix + key) in obj
}
return false
},
// enumerate(obj) {
// console.log('[jsproxy] %s enumerate: %s', name)
// // TODO:
// },
ownKeys(obj) {
// console.log('[jsproxy] %s ownKeys', name)
return getAllKeys()
},
// defineProperty(obj, key, desc) {
// // console.log('[jsproxy] %s defineProperty: %s', name, key)
// // TODO:
// },
getOwnPropertyDescriptor(obj, key) {
// console.log('[jsproxy] %s getOwnPropertyDescriptor: %s', name, key)
if (typeof key === 'string') {
return getOwnPropertyDescriptor(raw, prefix + key)
}
}
})
defineProperty(win, name, {value: storage})
}
/**
* @param {WindowOrWorkerGlobalScope} global
* @param {string} origin
*/
export function createStorage(global, origin) {
const prefixStr = origin + '$'
const prefixLen = prefixStr.length
function delPrefix(str) {
return str.substr(prefixLen)
}
function delPrefixGetter(oldFn) {
return function() {
const val = oldFn.call(this)
return val && delPrefix(val)
}
}
//
// Web Storage
//
setup(global, 'localStorage', prefixStr)
setup(global, 'sessionStorage', prefixStr)
const StorageEventProto = global['StorageEvent'].prototype
hook.prop(StorageEventProto, 'key', delPrefixGetter)
hook.prop(StorageEventProto, 'url',
getter => function() {
const val = getter.call(this)
return urlx.decUrlStrAbs(val)
}
)
// TODO: StorageEventProto.storageArea
//
// Storage API
//
function addPrefixHook(oldFn) {
return function(name) {
if (arguments.length > 0) {
arguments[0] = prefixStr + name
}
return apply(oldFn, this, arguments)
}
}
// indexedDB
const IDBFactoryProto = global['IDBFactory'].prototype
hook.func(IDBFactoryProto, 'open', addPrefixHook)
hook.func(IDBFactoryProto, 'databases', oldFn => async function() {
/** @type { {name: string, version: number}[] } */
const arr = await apply(oldFn, this, arguments)
const ret = []
for (const v of arr) {
if (v.name[0] !== '.') {
v.name = delPrefix(v.name)
ret.push(v)
}
}
return ret
})
// delete
hook.func(IDBFactoryProto, 'deleteDatabase', addPrefixHook)
const IDBDatabaseProto = global['IDBDatabase'].prototype
hook.prop(IDBDatabaseProto, 'name', delPrefixGetter)
// Cache Storage
const cacheStorageProto = global['CacheStorage'].prototype
hook.func(cacheStorageProto, 'open', addPrefixHook)
hook.func(cacheStorageProto, 'keys', oldFn => async function() {
/** @type {string[]} */
const arr = await apply(oldFn, this, arguments)
const ret = []
for (const v of arr) {
if (v[0] !== '.') {
ret.push(delPrefix(v))
}
}
return ret
})
hook.func(cacheStorageProto, 'delete', addPrefixHook)
// WebSQL
hook.func(global, 'openDatabase', addPrefixHook)
}
================================================
FILE: src/proxy/src/sw.js
================================================
import * as path from './path.js'
import * as route from './route.js'
import * as urlx from './urlx.js'
import * as util from './util.js'
import * as cookie from './cookie.js'
import * as network from './network.js'
import * as MSG from './msg.js'
import * as jsfilter from './jsfilter.js'
import * as inject from './inject.js'
import {Signal} from './signal.js'
import {Database} from './database.js'
const CONF_UPDATE_TIMER = 1000 * 60 * 5
let mConf
const MAX_REDIR = 5
/** @type {ServiceWorkerGlobalScope} */
// @ts-ignore
const global = self
const clients = global.clients
let mUrlHandler
/**
* @param {*} target
* @param {number} cmd
* @param {*=} val
*/
function sendMsg(target, cmd, val) {
if (target) {
target.postMessage([cmd, val])
} else {
console.warn('invalid target', cmd, val)
}
}
// 也可以用 clientId 关联,但兼容性不高
let pageCounter = 0
/** @type {Map<number, [Signal, number]>} */
const pageWaitMap = new Map()
function genPageId() {
return ++pageCounter
}
/**
* @param {number} pageId
*/
function pageWait(pageId) {
const s = new Signal()
// 设置最大等待时间
// 有些页面不会执行 JS(例如查看源文件),导致永久等待
const timer = setTimeout(_ => {
pageWaitMap.delete(pageId)
s.notify(false)
}, 2000)
pageWaitMap.set(pageId, [s, timer])
return s.wait()
}
/**
* @param {number} id
* @param {boolean} isDone
*/
function pageNotify(id, isDone) {
const arr = pageWaitMap.get(id)
if (!arr) {
console.warn('[jsproxy] unknown page id:', id)
return
}
const [s, timer] = arr
if (isDone) {
pageWaitMap.delete(id)
s.notify(true)
} else {
// 页面已开始初始化,关闭定时器
clearTimeout(timer)
}
}
function makeHtmlRes(body, status = 200) {
return new Response(body, {
status,
headers: {
'content-type': 'text/html; charset=utf-8',
}
})
}
/**
* @param {Response} res
* @param {ResponseInit} resOpt
* @param {URL} urlObj
*/
function processHtml(res, resOpt, urlObj) {
const reader = res.body.getReader()
let injected = false
const stream = new ReadableStream({
async pull(controller) {
if (!injected) {
injected = true
// 注入页面顶部的代码
const pageId = genPageId()
const buf = inject.getHtmlCode(urlObj, pageId)
controller.enqueue(buf)
// 留一些时间给页面做异步初始化
const done = await pageWait(pageId)
if (!done) {
console.warn('[jsproxy] page wait timeout. id: %d url: %s',
pageId, urlObj.href)
}
}
const r = await reader.read()
if (r.done) {
controller.close()
} else {
controller.enqueue(r.value)
}
}
})
return new Response(stream, resOpt)
}
/**
* @param {ArrayBuffer} buf
* @param {string} charset
*/
function processJs(buf, charset) {
const u8 = new Uint8Array(buf)
const ret = jsfilter.parseBin(u8, charset) || u8
return util.concatBufs([inject.getWorkerCode(), ret])
}
/**
* @param {*} cmd
* @param {*} msg
* @param {string=} srcId
*/
async function sendMsgToPages(cmd, msg, srcId) {
// 通知页面更新 Cookie
const pages = await clients.matchAll({type: 'window'})
for (const page of pages) {
if (page.frameType !== 'top-level') {
continue
}
if (srcId && page.id === srcId) {
continue
}
sendMsg(page, cmd, msg)
}
}
/** @type Map<string, string> */
const mIdUrlMap = new Map()
/**
* @param {string} id
*/
async function getUrlByClientId(id) {
const client = await clients.get(id)
if (!client) {
return
}
const urlStr = urlx.decUrlStrAbs(client.url)
mIdUrlMap.set(id, urlStr)
return urlStr
}
/**
* @param {string} jsonStr
* @param {number} status
* @param {URL} urlObj
*/
function parseGatewayError(jsonStr, status, urlObj) {
let ret = ''
const {
msg, addr, url
} = JSON.parse(jsonStr)
switch (status) {
case 204:
switch (msg) {
case 'ORIGIN_NOT_ALLOWED':
ret = '当前域名不在服务器外链白名单'
break
case 'CIRCULAR_DEPENDENCY':
ret = '当前请求出现循环代理'
break
case 'SITE_MOVE':
ret = `当前站点移动到: <a href="${url}">${url}</a>`
break
}
break
case 500:
ret = '代理服务器内部错误'
break
case 502:
if (addr) {
ret = `代理服务器无法连接网站 ${urlObj.origin} (${addr})`
} else {
ret = `代理服务器无法解析域名 ${urlObj.host}`
}
break
case 504:
ret = `代理服务器连接网站超时 ${urlObj.origin}`
if (addr) {
ret += ` (${addr})`
}
break
}
return makeHtmlRes(ret)
}
/**
* @param {Request} req
* @param {URL} urlObj
* @param {URL} cliUrlObj
* @param {number} redirNum
* @returns {Promise<Response>}
*/
async function forward(req, urlObj, cliUrlObj, redirNum) {
const r = await network.launch(req, urlObj, cliUrlObj)
if (!r) {
return makeHtmlRes('load fail')
}
let {
res, status, headers, cookies
} = r
if (cookies) {
sendMsgToPages(MSG.SW_COOKIE_PUSH, cookies)
}
if (!status) {
status = res.status || 200
}
let headersMutable = true
if (!headers) {
headers = res.headers
headersMutable = false
}
/**
* @param {string} k
* @param {string} v
*/
const setHeader = (k, v) => {
if (!headersMutable) {
headers = new Headers(headers)
headersMutable = true
}
headers.set(k, v)
}
// 网关错误
const gwErr = headers.get('gateway-err--')
if (gwErr) {
return parseGatewayError(gwErr, status, urlObj)
}
/** @type {ResponseInit} */
const resOpt = {status, headers}
// 空响应
// https://fetch.spec.whatwg.org/#statuses
if (status === 101 ||
status === 204 ||
status === 205 ||
status === 304
) {
return new Response(null, resOpt)
}
// 处理重定向
if (status === 301 ||
status === 302 ||
status === 303 ||
status === 307 ||
status === 308
) {
const locStr = headers.get('location')
const locObj = locStr && urlx.newUrl(locStr, urlObj)
if (locObj) {
// 跟随模式,返回最终数据
if (req.redirect === 'follow') {
if (++redirNum === MAX_REDIR) {
return makeHtmlRes('重定向过多', 500)
}
return forward(req, locObj, cliUrlObj, redirNum)
}
// 不跟随模式(例如页面跳转),返回 30X 状态
setHeader('location', urlx.encUrlObj(locObj))
}
// firefox, safari 保留内容会提示页面损坏
return new Response(null, resOpt)
}
//
// 提取 mime 和 charset(不存在则为 undefined)
// 可能存在多个段,并且值可能包含引号。例如:
// content-type: text/html; ...; charset="gbk"
//
const ctVal = headers.get('content-type') || ''
const [, mime, charset] = ctVal
.toLocaleLowerCase()
.match(/([^;]*)(?:.*?charset=['"]?([^'"]+))?/)
const type = req.destination
if (type === 'script' ||
type === 'worker' ||
type === 'sharedworker'
) {
const buf = await res.arrayBuffer()
const ret = processJs(buf, charset)
setHeader('content-type', 'text/javascript')
return new Response(ret, resOpt)
}
if (req.mode === 'navigate' && mime === 'text/html') {
return processHtml(res, resOpt, urlObj)
}
return new Response(res.body, resOpt)
}
async function proxy(e, urlObj) {
// 使用 e.resultingClientId 有问题
const id = e.clientId
let cliUrlStr
if (id) {
cliUrlStr = mIdUrlMap.get(id) || await getUrlByClientId(id)
}
if (!cliUrlStr) {
cliUrlStr = urlObj.href
}
const cliUrlObj = new URL(cliUrlStr)
try {
return await forward(e.request, urlObj, cliUrlObj, 0)
} catch (err) {
console.error(err)
return makeHtmlRes('前端脚本错误<br><pre>' + err.stack + '</pre>', 500)
}
}
/** @type {Database} */
let mDB
async function initDB() {
mDB = new Database('.sys')
await mDB.open({
'url-cache': {
keyPath: 'url'
},
'cookie': {
keyPath: 'id'
}
})
await network.setDB(mDB)
await cookie.setDB(mDB)
}
/**
* @param {FetchEvent} e
*/
async function onFetch(e) {
if (!mConf) {
await initConf()
}
// TODO: 逻辑优化
if (!mDB) {
await initDB()
}
const req = e.request
const urlStr = urlx.delHash(req.url)
// 首页(例如 https://zjcqoo.github.io/)
if (urlStr === path.ROOT || urlStr === path.HOME) {
let indexPath = mConf.assets_cdn + mConf.index_path
if (!mConf.index_path) {
// 临时代码。防止配置文件未更新的情况下首页无法加载
indexPath = mConf.assets_cdn + 'index_v3.html'
}
const res = await fetch(indexPath)
return makeHtmlRes(res.body)
}
// 图标、配置(例如 https://zjcqoo.github.io/conf.js)
if (urlStr === path.CONF || urlStr === path.ICON) {
return fetch(urlStr)
}
// 注入页面的脚本(例如 https://zjcqoo.github.io/__sys__/helper.js)
if (urlStr === path.HELPER) {
return fetch(self['__FILE__'])
}
// 静态资源(例如 https://zjcqoo.github.io/__sys__/assets/ico/google.png)
if (urlStr.startsWith(path.ASSETS)) {
const filePath = urlStr.substr(path.ASSETS.length)
return fetch(mConf.assets_cdn + filePath)
}
if (req.mode === 'navigate') {
const newUrl = urlx.adjustNav(urlStr)
if (newUrl) {
return Response.redirect(newUrl, 301)
}
}
let targetUrlStr = urlx.decUrlStrAbs(urlStr)
const handler = mUrlHandler[targetUrlStr]
if (handler) {
const {
redir,
content,
replace,
} = handler
if (redir) {
return Response.redirect('/-----' + redir)
}
if (content) {
return makeHtmlRes(content)
}
if (replace) {
targetUrlStr = replace
}
}
const targetUrlObj = urlx.newUrl(targetUrlStr)
if (targetUrlObj) {
return proxy(e, targetUrlObj)
}
return makeHtmlRes('invalid url: ' + targetUrlStr, 500)
}
function parseUrlHandler(handler) {
const map = {}
if (!handler) {
return map
}
for (const [match, rule] of Object.entries(handler)) {
// TODO: 支持通配符和正则
map[match] = rule
}
return map
}
// TODO: 逻辑优化
function updateConf(conf, force) {
if (!force && mConf) {
if (conf.ver <= mConf.ver) {
return
}
if (conf.node_map[mConf.node_default]) {
conf.node_default = mConf.node_default
} else {
console.warn('default node %s -> %s',
mConf.node_default, conf.node_default)
}
sendMsgToPages(MSG.SW_CONF_CHANGE, mConf)
}
inject.setConf(conf)
route.setConf(conf)
network.setConf(conf)
mUrlHandler = parseUrlHandler(conf.url_handler)
/*await*/ saveConf(conf)
mConf = conf
}
async function readConf() {
const cache = await caches.open('.sys')
const req = new Request('/conf.json')
const res = await cache.match(req)
if (res) {
return res.json()
}
}
async function saveConf(conf) {
const json = JSON.stringify(conf)
const cache = await caches.open('.sys')
const req = new Request('/conf.json')
const res = new Response(json);
return cache.put(req, res)
}
async function loadConf() {
const res = await fetch('conf.js')
const txt = await res.text()
self['jsproxy_config'] = updateConf
Function(txt)()
}
/** @type {Signal[]} */
let mConfInitQueue
async function initConf() {
if (mConfInitQueue) {
const s = new Signal()
mConfInitQueue.push(s)
return s.wait()
}
mConfInitQueue = []
let conf
try {
conf = await readConf()
} catch (err) {
console.warn('load conf fail:', err)
}
if (!conf) {
conf = self['__CONF__']
}
if (conf) {
updateConf(conf)
} else {
conf = await loadConf()
}
// 定期更新配置
setInterval(loadConf, CONF_UPDATE_TIMER)
mConfInitQueue.forEach(s => s.notify())
mConfInitQueue = null
}
global.addEventListener('fetch', e => {
e.respondWith(onFetch(e))
})
global.addEventListener('message', e => {
// console.log('sw msg:', e.data)
const [cmd, val] = e.data
const src = e.source
switch (cmd) {
case MSG.PAGE_COOKIE_PUSH:
cookie.set(val)
// @ts-ignore
sendMsgToPages(MSG.SW_COOKIE_PUSH, [val], src.id)
break
case MSG.PAGE_INFO_PULL:
// console.log('SW MSG.COOKIE_PULL:', src.id)
sendMsg(src, MSG.SW_INFO_PUSH, {
cookies: cookie.getNonHttpOnlyItems(),
conf: mConf,
})
break
case MSG.PAGE_INIT_BEG:
// console.log('SW MSG.PAGE_INIT_BEG:', val)
pageNotify(val, false)
break
case MSG.PAGE_INIT_END:
// console.log('SW MSG.PAGE_INIT_END:', val)
pageNotify(val, true)
break
case MSG.PAGE_CONF_GET:
if (mConf) {
sendMsg(src, MSG.SW_CONF_RETURN, mConf)
} else {
initConf().then(_ => {
sendMsg(src, MSG.SW_CONF_RETURN, mConf)
})
}
break
case MSG.PAGE_CONF_SET:
updateConf(val, true)
sendMsgToPages(MSG.SW_CONF_CHANGE, mConf)
break
case MSG.PAGE_RELOAD_CONF:
/*await*/ loadConf()
break
case MSG.PAGE_READY_CHECK:
sendMsg(src, MSG.SW_READY)
/*await*/ loadConf()
break
}
})
global.addEventListener('install', e => {
console.log('oninstall:', e)
e.waitUntil(global.skipWaiting())
})
global.addEventListener('activate', e => {
console.log('onactivate:', e)
sendMsgToPages(MSG.SW_READY, 1)
e.waitUntil(clients.claim())
})
console.log('[jsproxy] sw inited')
================================================
FILE: src/proxy/src/tld-data.js
================================================
// THIS FILE WAS GENERATED BY './tools/tld/gen.js'
export default '0.bg,001www.com,0emm.com,1.bg,12hp.at,12hp.ch,12hp.de,1337.pictures,16-b.it,1kapp.com,2.bg,2000.hu,2038.io,2ix.at,2ix.ch,2ix.de,3.bg,32-b.it,3utilities.com,4.bg,4lima.at,4lima.ch,4lima.de,4u.com,5.bg,6.bg,64-b.it,7.bg,8.bg,9.bg,9guacu.br,a.bg,a.prod.fastly.net,a.run.app,a.se,a.ssl.fastly.net,aa.no,aaa,aaa.pro,aarborte.no,aarp,ab.ca,abarth,abashiri.hokkaido.jp,abb,abbott,abbvie,abc,abc.br,abeno.osaka.jp,abiko.chiba.jp,abira.hokkaido.jp,abkhazia.su,able,abo.pa,abogado,abr.it,abruzzo.it,abu.yamaguchi.jp,abudhabi,ac,ac.ae,ac.at,ac.be,ac.ci,ac.cn,ac.cr,ac.cy,ac.gn,ac.gov.br,ac.id,ac.il,ac.im,ac.in,ac.ir,ac.jp,ac.ke,ac.kr,ac.leg.br,ac.lk,ac.ls,ac.ma,ac.me,ac.mu,ac.mw,ac.mz,ac.ni,ac.nz,ac.pa,ac.pr,ac.rs,ac.ru,ac.rw,ac.se,ac.sz,ac.th,ac.tj,ac.tz,ac.ug,ac.uk,ac.vn,ac.za,ac.zm,ac.zw,aca.pro,academia.bo,academy,academy.museum,accenture,accesscam.org,accident-investigation.aero,accident-prevention.aero,accountant,accountants,acct.pro,achi.nagano.jp,aco,act.au,act.edu.au,actor,ad,ad.jp,adac,adachi.tokyo.jp,adm.br,ads,adult,adult.ht,adv.br,adv.mz,advisor.ws,adygeya.ru,adygeya.su,ae,ae.org,aeg,aejrie.no,aero,aero.mv,aero.tt,aerobatic.aero,aeroclub.aero,aerodrome.aero,aeroport.fr,aetna,af,afamilycompany,afjord.no,afl,africa,africa.com,ag,ag.it,aga.niigata.jp,agakhan,agano.niigata.jp,agdenes.no,agematsu.nagano.jp,agency,agents.aero,agr.br,agrar.hu,agric.za,agriculture.museum,agrigento.it,agrinet.tn,agro.bo,agro.pl,aguni.okinawa.jp,ah.cn,ah.no,ai,aibetsu.hokkaido.jp,aichi.jp,aid.pl,aig,aigo,aikawa.kanagawa.jp,ainan.ehime.jp,aioi.hyogo.jp,aip.ee,air-surveillance.aero,air-traffic-control.aero,air.museum,airbus,aircraft.aero,airforce,airguard.museum,airline.aero,airport.aero,airtel,airtraffic.aero,aisai.aichi.jp,aisho.shiga.jp,aizubange.fukushima.jp,aizumi.tokushima.jp,aizumisato.fukushima.jp,aizuwakamatsu.fukushima.jp,aju.br,ak.us,akabira.hokkaido.jp,akagi.shimane.jp,akaiwa.okayama.jp,akashi.hyogo.jp,akdn,aki.kochi.jp,akiruno.tokyo.jp,akishima.tokyo.jp,akita.akita.jp,akita.jp,akkeshi.hokkaido.jp,aknoluokta.no,ako.hyogo.jp,akrehamn.no,aktyubinsk.su,akune.kagoshima.jp,al,al.eu.org,al.gov.br,al.it,al.leg.br,al.no,al.us,alabama.museum,alaheadju.no,aland.fi,alaska.museum,alces.network,alessandria.it,alesund.no,alfaromeo,algard.no,alibaba,alipay,allfinanz,allstate,ally,alpha-myqnapcloud.com,alpha.bounty-full.com,alsace,alstahaug.no,alstom,alt.za,alta.no,alto-adige.it,altoadige.it,alvdal.no,alwaysdata.net,am,am.br,am.gov.br,am.leg.br,ama.aichi.jp,ama.shimane.jp,amagasaki.hyogo.jp,amakusa.kumamoto.jp,amami.kagoshima.jp,amber.museum,ambulance.aero,ambulance.museum,american.museum,americana.museum,americanantiques.museum,americanart.museum,americanexpress,americanfamily,amex,amfam,ami.ibaraki.jp,amica,amli.no,amot.no,amsterdam,amsterdam.museum,amusement.aero,an.it,analytics,anamizu.ishikawa.jp,anan.nagano.jp,anan.tokushima.jp,anani.br,ancona.it,and.mom,and.museum,andasuolo.no,andebu.no,ando.nara.jp,andoy.no,andria-barletta-trani.it,andria-trani-barletta.it,andriabarlettatrani.it,andriatranibarletta.it,android,andøy.no,anjo.aichi.jp,ann-arbor.mi.us,annaka.gunma.jp,annefrank.museum,anpachi.gifu.jp,anquan,anthro.museum,anthropology.museum,antiques.museum,anz,ao,ao.it,aogaki.hyogo.jp,aogashima.tokyo.jp,aoki.nagano.jp,aol,aomori.aomori.jp,aomori.jp,aosta-valley.it,aosta.it,aostavalley.it,aoste.it,ap-northeast-1.elasticbeanstalk.com,ap-northeast-2.elasticbeanstalk.com,ap-northeast-3.elasticbeanstalk.com,ap-south-1.elasticbeanstalk.com,ap-southeast-1.elasticbeanstalk.com,ap-southeast-2.elasticbeanstalk.com,ap.gov.br,ap.gov.pl,ap.it,ap.leg.br,aparecida.br,apartments,api.stdlib.com,apigee.io,app,app.banzaicloud.io,app.lmpm.com,app.os.fedoraproject.org,app.os.stg.fedoraproject.org,app.render.com,appchizi.com,apple,applicationcloud.io,applinzi.com,apps.fbsbx.com,apps.lair.io,appspot.com,aq,aq.it,aquarelle,aquarium.museum,aquila.it,ar,ar.com,ar.it,ar.us,arab,arai.shizuoka.jp,arakawa.saitama.jp,arakawa.tokyo.jp,aramco,arao.kumamoto.jp,arboretum.museum,archaeological.museum,archaeology.museum,archi,architecture.museum,ardal.no,aremark.no,arendal.no,arezzo.it,ariake.saga.jp,arida.wakayama.jp,aridagawa.wakayama.jp,arita.saga.jp,arkhangelsk.su,armenia.su,army,arna.no,arpa,arq.br,art,art.br,art.do,art.dz,art.ht,art.museum,art.pl,art.sn,artanddesign.museum,artcenter.museum,artdeco.museum,arte,arte.bo,arteducation.museum,artgallery.museum,arts.co,arts.museum,arts.nf,arts.ro,arts.ve,artsandcrafts.museum,arvo.network,as,as.us,asago.hyogo.jp,asahi.chiba.jp,asahi.ibaraki.jp,asahi.mie.jp,asahi.nagano.jp,asahi.toyama.jp,asahi.yamagata.jp,asahikawa.hokkaido.jp,asaka.saitama.jp,asakawa.fukushima.jp,asakuchi.okayama.jp,asaminami.hiroshima.jp,ascoli-piceno.it,ascolipiceno.it,asda,aseral.no,ashgabad.su,ashibetsu.hokkaido.jp,ashikaga.tochigi.jp,ashiya.fukuoka.jp,ashiya.hyogo.jp,ashoro.hokkaido.jp,asia,asker.no,askim.no,askoy.no,askvoll.no,askøy.no,asmatart.museum,asn.au,asn.lv,asnes.no,aso.kumamoto.jp,ass.km,assabu.hokkaido.jp,assassination.museum,assisi.museum,assn.lk,asso.bj,asso.ci,asso.dz,asso.eu.org,asso.fr,asso.gp,asso.ht,asso.km,asso.mc,asso.nc,asso.re,associates,association.aero,association.museum,asti.it,astronomy.museum,asuke.aichi.jp,at,at-band-camp.net,at.eu.org,at.it,atami.shizuoka.jp,ath.cx,athleta,atlanta.museum,atm.pl,ato.br,atsugi.kanagawa.jp,atsuma.hokkaido.jp,attorney,au,au.eu.org,auction,audi,audible,audio,audnedaln.no,augustow.pl,aukra.no,aure.no,aurland.no,aurskog-holand.no,aurskog-høland.no,auspost,austevoll.no,austin.museum,australia.museum,austrheim.no,author,author.aero,auto,auto.pl,automotive.museum,autos,av.it,av.tr,avellino.it,averoy.no,averøy.no,avianca,aviation.museum,avocat.fr,avocat.pro,avoues.fr,aw,awaji.hyogo.jp,awdev.ca,aws,ax,axa,axis.museum,aya.miyazaki.jp,ayabe.kyoto.jp,ayagawa.kagawa.jp,ayase.kanagawa.jp,az,az.us,azerbaijan.su,azimuth.network,azumino.nagano.jp,azure,azure-mobile.net,azurecontainer.io,azurewebsites.net,aéroport.ci,b-data.io,b.bg,b.br,b.se,b.ssl.fastly.net,ba,ba.gov.br,ba.it,ba.leg.br,babia-gora.pl,baby,backplaneapp.io,badaddja.no,badajoz.museum,baghdad.museum,bahcavuotna.no,bahccavuotna.no,bahn.museum,baidar.no,baidu,bajddar.no,balashov.su,balat.no,bale.museum,balena-devices.com,balestrand.no,ballangen.no,ballooning.aero,balsan-sudtirol.it,balsan-suedtirol.it,balsan-südtirol.it,balsan.it,balsfjord.no,baltimore.museum,bamble.no,banamex,bananarepublic,band,bandai.fukushima.jp,bando.ibaraki.jp,bank,bar,bar.pro,barcelona,barcelona.museum,barclaycard,barclays,bardu.no,barefoot,bargains,bari.it,barletta-trani-andria.it,barlettatraniandria.it,barreau.bj,barrel-of-knowledge.info,barrell-of-knowledge.info,barsy.bg,barsy.ca,barsy.club,barsy.co.uk,barsy.de,barsy.eu,barsy.in,barsy.info,barsy.io,barsy.me,barsy.menu,barsy.mobi,barsy.net,barsy.online,barsy.org,barsy.pro,barsy.pub,barsy.shop,barsy.site,barsy.support,barsy.uk,barsycenter.com,barsyonline.co.uk,barsyonline.com,barueri.br,barum.no,bas.it,baseball,baseball.museum,basel.museum,bashkiria.ru,bashkiria.su,basicserver.io,basilicata.it,basketball,baths.museum,bato.tochigi.jp,batsfjord.no,bauern.museum,bauhaus,bayern,bb,bbc,bbs.tr,bbt,bbva,bc.ca,bcg,bci.dnstrace.pro,bcn,bd,bd.se,be,be.eu.org,bearalvahki.no,bearalváhki.no,beardu.no,beats,beauty,beauxarts.museum,bedzin.pl,beeldengeluid.museum,beep.pl,beer,beiarn.no,bel.tr,belau.pw,belem.br,bellevue.museum,belluno.it,benevento.it,bentley,beppu.oita.jp,berg.no,bergamo.it,bergbau.museum,bergen.no,berkeley.museum,berlevag.no,berlevåg.no,berlin,berlin.museum,bern.museum,beskidy.pl,best,bestbuy,bet,beta.bounty-full.com,betainabox.com,better-than.tv,bf,bg,bg.eu.org,bg.it,bh,bharti,bhz.br,bi,bi.it,bialowieza.pl,bialystok.pl,bibai.hokkaido.jp,bible,bible.museum,bid,biei.hokkaido.jp,bielawa.pl,biella.it,bieszczady.pl,bievat.no,bievát.no,bifuka.hokkaido.jp,bihoro.hokkaido.jp,bike,bilbao.museum,bill.museum,bindal.no,bing,bingo,bio,bio.br,bir.ru,biratori.hokkaido.jp,birdart.museum,birkenes.no,birthplace.museum,bitballoon.com,biz,biz.at,biz.az,biz.bb,biz.cy,biz.dk,biz.et,biz.gl,biz.id,biz.ki,biz.ls,biz.mv,biz.mw,biz.ni,biz.nr,biz.pk,biz.pl,biz.pr,biz.tj,biz.tr,biz.tt,biz.ua,biz.vn,biz.zm,bizen.okayama.jp,bj,bj.cn,bjarkoy.no,bjarkøy.no,bjerkreim.no,bjugn.no,bl.it,black,blackbaudcdn.net,blackfriday,blockbuster,blog,blog.bo,blog.br,blogdns.com,blogdns.net,blogdns.org,blogsite.org,blogsite.xyz,blogspot.ae,blogspot.al,blogspot.am,blogspot.ba,blogspot.be,blogspot.bg,blogspot.bj,blogspot.ca,blogspot.cf,blogspot.ch,blogspot.cl,blogspot.co.at,blogspot.co.id,blogspot.co.il,blogspot.co.ke,blogspot.co.nz,blogspot.co.uk,blogspot.co.za,blogspot.com,blogspot.com.ar,blogspot.com.au,blogspot.com.br,blogspot.com.by,blogspot.com.co,blogspot.com.cy,blogspot.com.ee,blogspot.com.eg,blogspot.com.es,blogspot.com.mt,blogspot.com.ng,blogspot.com.tr,blogspot.com.uy,blogspot.cv,blogspot.cz,blogspot.de,blogspot.dk,blogspot.fi,blogspot.fr,blogspot.gr,blogspot.hk,blogspot.hr,blogspot.hu,blogspot.ie,blogspot.in,blogspot.is,blogspot.it,blogspot.jp,blogspot.kr,blogspot.li,blogspot.lt,blogspot.lu,blogspot.md,blogspot.mk,blogspot.mr,blogspot.mx,blogspot.my,blogspot.nl,blogspot.no,blogspot.pe,blogspot.pt,blogspot.qa,blogspot.re,blogspot.ro,blogspot.rs,blogspot.ru,blogspot.se,blogspot.sg,blogspot.si,blogspot.sk,blogspot.sn,blogspot.td,blogspot.tw,blogspot.ug,blogspot.vn,blogsyte.com,bloomberg,bloxcms.com,blue,bm,bmd.br,bmoattachments.org,bms,bmw,bn,bn.it,bnl,bnpparibas,bnr.la,bo,bo.it,bo.nordland.no,bo.telemark.no,boats,boavista.br,bodo.no,bodø.no,boehringer,bofa,bokn.no,boldlygoingnowhere.org,boleslawiec.pl,bolivia.bo,bologna.it,bolt.hu,bolzano-altoadige.it,bolzano.it,bom,bomlo.no,bond,bonn.museum,boo,book,booking,boomla.net,bosch,bostik,boston,boston.museum,bot,botanical.museum,botanicalgarden.museum,botanicgarden.museum,botany.museum,bounceme.net,bounty-full.com,boutique,box,boxfuse.io,bozen-sudtirol.it,bozen-suedtirol.it,bozen-südtirol.it,bozen.it,bpl.biz,bplaced.com,bplaced.de,bplaced.net,br,br.com,br.it,bradesco,brand.se,brandywinevalley.museum,brasil.museum,brasilia.me,bremanger.no,brescia.it,bridgestone,brindisi.it,bristol.museum,british.museum,britishcolumbia.museum,broadcast.museum,broadway,broke-it.net,broker,broker.aero,bronnoy.no,bronnoysund.no,brother,browsersafetymark.io,brumunddal.no,brunel.museum,brussel.museum,brussels,brussels.museum,bruxelles.museum,bryansk.su,bryne.no,brønnøy.no,brønnøysund.no,bs,bs.it,bsb.br,bss.design,bt,bt.it,bu.no,budapest,budejju.no,bugatti,build,builders,building.museum,bukhara.su,bulsan-sudtirol.it,bulsan-suedtirol.it,bulsan-südtirol.it,bulsan.it,bungoono.oita.jp,bungotakada.oita.jp,bunkyo.tokyo.jp,burghof.museum,bus.museum,busan.kr,bushey.museum,business,buy,buyshouses.net,buzen.fukuoka.jp,buzz,bv,bw,by,bydgoszcz.pl,byen.site,bygland.no,bykle.no,bytom.pl,bz,bz.it,bzh,bzz.dapps.earth,báhcavuotna.no,báhccavuotna.no,báidár.no,bájddar.no,bálát.no,bådåddjå.no,båtsfjord.no,bærum.no,bø.nordland.no,bø.telemark.no,bømlo.no,c.bg,c.cdn77.org,c.la,c.se,c66.me,ca,ca-central-1.elasticbeanstalk.com,ca.eu.org,ca.it,ca.na,ca.us,caa.aero,caa.li,cab,cable-modem.org,cadaques.museum,cafe,cagliari.it,cahcesuolo.no,cal,cal.it,calabria.it,california.museum,call,caltanissetta.it,calvinklein,cam,cam.it,cambridge.museum,camdvr.org,camera,camp,campania.it,campidano-medio.it,campidanomedio.it,campinagrande.br,campinas.br,campobasso.it,can.museum,canada.museum,cancerresearch,canon,capebreton.museum,capetown,capital,capitalone,car,caravan,carbonia-iglesias.it,carboniaiglesias.it,cards,care,career,careers,cargo.aero,carrara-massa.it,carraramassa.it,carrd.co,carrier.museum,cars,cartier,cartoonart.museum,casa,casacam.net,casadelamoneda.museum,case,caseih,caserta.it,cash,casino,casino.hu,castle.museum,castres.museum,cat,catania.it,catanzaro.it,catering,catering.aero,catholic,caxias.br,cb.it,cba,cbg.ru,cbn,cbre,cbs,cc,cc.ak.us,cc.al.us,cc.ar.us,cc.as.us,cc.az.us,cc.ca.us,cc.co.us,cc.ct.us,cc.dc.us,cc.de.us,cc.fl.us,cc.ga.us,cc.gu.us,cc.hi.us,cc.ia.us,cc.id.us,cc.il.us,cc.in.us,cc.ks.us,cc.ky.us,cc.la.us,cc.ma.us,cc.md.us,cc.me.us,cc.mi.us,cc.mn.us,cc.mo.us,cc.ms.us,cc.mt.us,cc.na,cc.nc.us,cc.nd.us,cc.ne.us,cc.nh.us,cc.nj.us,cc.nm.us,cc.nv.us,cc.ny.us,cc.oh.us,cc.ok.us,cc.or.us,cc.pa.us,cc.pr.us,cc.ri.us,cc.sc.us,cc.sd.us,cc.tn.us,cc.tx.us,cc.ua,cc.ut.us,cc.va.us,cc.vi.us,cc.vt.us,cc.wa.us,cc.wi.us,cc.wv.us,cc.wy.us,cci.fr,cd,cd.eu.org,cdn77-ssl.net,ce.gov.br,ce.it,ce.leg.br,ceb,cechire.com,celtic.museum,center,center.museum,ceo,cern,certification.aero,certmgr.org,cesena-forli.it,cesena-forlì.it,cesenaforli.it,cesenaforlì.it,cf,cfa,cfd,cg,ch,ch.eu.org,ch.it,chambagri.fr,championship.aero,chanel,channel,channelsdvr.net,charity,charter.aero,chase,chat,chattanooga.museum,cheap,cheltenham.museum,cherkassy.ua,cherkasy.ua,chernigov.ua,chernihiv.ua,chernivtsi.ua,chernovtsy.ua,chesapeakebay.museum,chiba.jp,chicago.museum,chichibu.saitama.jp,chieti.it,chigasaki.kanagawa.jp,chihayaakasaka.osaka.jp,chijiwa.nagasaki.jp,chikugo.fukuoka.jp,chikuho.fukuoka.jp,chikuhoku.nagano.jp,chikujo.fukuoka.jp,chikuma.nagano.jp,chikusei.ibaraki.jp,chikushino.fukuoka.jp,chikuzen.fukuoka.jp,children.museum,childrens.museum,childrensgarden.museum,chimkent.su,chino.nagano.jp,chintai,chippubetsu.hokkaido.jp,chiropractic.museum,chirurgiens-dentistes-en-france.fr,chirurgiens-dentistes.fr,chiryu.aichi.jp,chita.aichi.jp,chitose.hokkaido.jp,chiyoda.gunma.jp,chiyoda.tokyo.jp,chizu.tottori.jp,chocolate.museum,chofu.tokyo.jp,chonan.chiba.jp,chosei.chiba.jp,choshi.chiba.jp,choyo.kumamoto.jp,christiansburg.museum,christmas,chrome,chrysler,chtr.k12.ma.us,chungbuk.kr,chungnam.kr,chuo.chiba.jp,chuo.fukuoka.jp,chuo.osaka.jp,chuo.tokyo.jp,chuo.yamanashi.jp,church,ci,ci.it,ciencia.bo,cieszyn.pl,cim.br,cincinnati.museum,cinema.museum,cipriani,circle,circus.museum,cisco,ciscofreak.com,cistron.nl,citadel,citi,citic,city,city.hu,cityeats,civilaviation.aero,civilisation.museum,civilization.museum,civilwar.museum,ck,ck.ua,cl,cl.it,claims,clan.rip,cleaning,cleverapps.io,click,clinic,clinique,clinton.museum,clock.museum,clothing,cloud,cloud.fedoraproject.org,cloud.goog,cloud.metacentrum.cz,cloud66.ws,cloud66.zone,cloudaccess.host,cloudaccess.net,cloudapp.net,cloudapps.digital,cloudcontrolapp.com,cloudcontrolled.com,cloudeity.net,cloudera.site,cloudfront.net,cloudfunctions.net,cloudns.asia,cloudns.biz,cloudns.cc,cloudns.club,cloudns.eu,cloudns.in,cloudns.info,cloudns.org,cloudns.pro,cloudns.pw,cloudns.us,cloudycluster.net,club,club.aero,club.tw,clubmed,cm,cn,cn-north-1.eb.amazonaws.com.cn,cn-northwest-1.eb.amazonaws.com.cn,cn.com,cn.eu.org,cn.it,cn.ua,cng.br,cnpy.gdn,cns.joyent.com,cnt.br,co,co.ae,co.ag,co.am,co.ao,co.at,co.bb,co.bi,co.bn,co.business,co.bw,co.ca,co.ci,co.cl,co.cm,co.com,co.cr,co.cz,co.dk,co.education,co.events,co.financial,co.gg,co.gl,co.gy,co.hu,co.id,co.il,co.im,co.in,co.ir,co.it,co.je,co.jp,co.ke,co.kr,co.krd,co.lc,co.ls,co.ma,co.me,co.mg,co.mu,co.mw,co.mz,co.na,co.network,co.ni,co.nl,co.no,co.nz,co.om,co.pl,co.place,co.pn,co.pw,co.rs,co.rw,co.st,co.sz,co.technology,co.th,co.tj,co.tm,co.tt,co.tz,co.ua,co.ug,co.uk,co.us,co.uz,co.ve,co.vi,co.za,co.zm,co.zw,coach,coal.museum,coastaldefence.museum,codes,codespot.com,cody.museum,coffee,cog.mi.us,col.ng,coldwar.museum,collection.museum,college,collegefan.org,cologne,colonialwilliamsburg.museum,coloradoplateau.museum,columbia.museum,columbus.museum,com,com.ac,com.af,com.ag,com.ai,com.al,com.am,com.ar,com.au,com.aw,com.az,com.ba,com.bb,com.bh,com.bi,com.bm,com.bn,com.bo,com.br,com.bs,com.bt,com.by,com.bz,com.ci,com.cm,com.cn,com.co,com.cu,com.cw,com.cy,com.de,com.dm,com.do,com.dz,com.ec,com.ee,com.eg,com.es,com.et,com.fr,com.ge,com.gh,com.gi,com.gl,com.gn,com.gp,com.gr,com.gt,com.gu,com.gy,com.hk,com.hn,com.hr,com.ht,com.im,com.io,com.iq,com.is,com.jo,com.kg,com.ki,com.km,com.kp,com.kw,com.ky,com.kz,com.la,com.lb,com.lc,com.lk,com.lr,com.lv,com.ly,com.mg,com.mk,com.ml,com.mo,com.ms,com.mt,com.mu,com.mv,com.mw,com.mx,com.my,com.na,com.nf,com.ng,com.ni,com.nr,com.om,com.pa,com.pe,com.pf,com.ph,com.pk,com.pl,com.pr,com.ps,com.pt,com.py,com.qa,com.re,com.ro,com.ru,com.sa,com.sb,com.sc,com.sd,com.se,com.sg,com.sh,com.sl,com.sn,com.so,com.st,com.sv,com.sy,com.tj,com.tm,com.tn,com.to,com.tr,com.tt,com.tw,com.ua,com.ug,com.uy,com.uz,com.vc,com.ve,com.vi,com.vn,com.vu,com.ws,com.zm,comcast,commbank,commune.am,communication.museum,communications.museum,community,community.museum,como.it,company,compare,compute-1.amazonaws.com,compute.amazonaws.com,compute.amazonaws.com.cn,compute.estate,computer,computer.museum,computerhistory.museum,comsec,comunicações.museum,condos,conf.au,conf.lv,conf.se,conference.aero,construction,consulado.st,consultant.aero,consulting,consulting.aero,contact,contagem.br,contemporary.museum,contemporaryart.museum,contractors,control.aero,convent.museum,cooking,cookingchannel,cool,coop,coop.br,coop.ht,coop.km,coop.mv,coop.mw,coop.py,coop.rw,coop.tt,cooperativa.bo,copenhagen.museum,corporation.museum,correios-e-telecomunicações.museum,corsica,corvette.museum,cosenza.it,costume.museum,couchpotatofries.org,council.aero,country,countryestate.museum,county.museum,coupon,coupons,courses,cpa,cpa.pro,cq.cn,cr,cr.it,cr.ua,crafting.xyz,crafts.museum,cranbrook.museum,crd.co,creation.museum,credit,creditcard,creditunion,cremona.it,crew.aero,cri.br,cri.nz,cricket,crimea.ua,crotone.it,crown,crs,cruise,cruises,cryptonomic.net,cs.it,csc,ct.it,ct.us,cu,cuiaba.br,cuisinella,cultural.museum,culturalcenter.museum,culture.museum,cuneo.it,cupcake.is,curitiba.br,cust.dev.thingdust.io,cust.disrec.thingdust.io,cust.prod.thingdust.io,cust.testing.thingdust.io,custom.metacentrum.cz,customer.enonic.io,customer.speedpartner.de,cv,cv.ua,cw,cx,cy,cy.eu.org,cya.gg,cyber.museum,cymru,cymru.museum,cyon.link,cyon.site,cyou,cz,cz.eu.org,cz.it,czeladz.pl,czest.pl,d.bg,d.gv.vc,d.se,dabur,dad,daegu.kr,daejeon.kr,dagestan.ru,dagestan.su,daigo.ibaraki.jp,daisen.akita.jp,daito.osaka.jp,daiwa.hiroshima.jp,dali.museum,dallas.museum,damnserver.com,dance,daplie.me,dapps.earth,data,database.museum,date,date.fukushima.jp,date.hokkaido.jp,dating,datsun,dattolocal.com,dattolocal.net,dattorelay.com,dattoweb.com,davvenjarga.no,davvenjárga.no,davvesiida.no,day,dazaifu.fukuoka.jp,dc.us,dclk,dd-dns.de,ddns.me,ddns.net,ddnsfree.com,ddnsgeek.com,ddnsking.com,ddnslive.com,ddnss.de,ddnss.org,ddr.museum,dds,de,de.com,de.cool,de.eu.org,de.us,deal,dealer,deals,deatnu.no,debian.net,decorativearts.museum,dedyn.io,def.br,defense.tn,definima.io,definima.net,degree,delaware.museum,delivery,dell,dell-ogliastra.it,dellogliastra.it,delmenhorst.museum,deloitte,delta,democracia.bo,democrat,demon.nl,denmark.museum,dental,dentist,dep.no,deporte.bo,depot.museum,desa.id,desi,design,design.aero,design.museum,detroit.museum,dev,dev-myqnapcloud.com,dev.static.land,development.run,devices.resinstaging.io,df.gov.br,df.leg.br,dgca.aero,dh.bytemark.co.uk,dhl,diamonds,dielddanuorri.no,diet,digital,dinosaur.museum,direct,directory,discount,discourse.group,discover,discovery.museum,dish,diskstation.eu,diskstation.me,diskstation.org,ditchyourip.com,divtasvuodna.no,divttasvuotna.no,diy,dj,dk,dk.eu.org,dlugoleka.pl,dm,dn.ua,dnepropetrovsk.ua,dni.us,dnipropetrovsk.ua,dnp,dnsalias.com,dnsalias.net,dnsalias.org,dnsdojo.com,dnsdojo.net,dnsdojo.org,dnsfor.me,dnshome.de,dnsiskinky.com,dnsking.ch,dnsup.net,dnsupdater.de,do,docs,doctor,dodge,does-it.net,doesntexist.com,doesntexist.org,dog,dolls.museum,domains,dominic.ua,donetsk.ua,donna.no,donostia.museum,dontexist.com,dontexist.net,dontexist.org,doomdns.com,doomdns.org,doshi.yamanashi.jp,dot,dovre.no,download,dp.ua,dr.na,dr.tr,drammen.no,drangedal.no,dray-dns.de,drayddns.com,draydns.de,dreamhosters.com,drive,drobak.no,drud.io,drud.us,drøbak.no,dscloud.biz,dscloud.me,dscloud.mobi,dsmynas.com,dsmynas.net,dsmynas.org,dst.mi.us,dtv,dubai,duck,duckdns.org,dunlop,duns,dupont,durban,durham.museum,dvag,dvr,dvrcam.info,dvrdns.org,dweb.link,dy.fi,dyn-berlin.de,dyn-ip24.de,dyn-o-saur.com,dyn-vpn.de,dyn.cosidns.de,dyn.ddnss.de,dyn.home-webserver.de,dyn53.io,dynalias.com,dynalias.net,dynalias.org,dynamic-dns.info,dynamisches-dns.de,dynathome.net,dyndns-at-home.com,dyndns-at-work.com,dyndns-blog.com,dyndns-free.com,dyndns-home.com,dyndns-ip.com,dyndns-mail.com,dyndns-office.com,dyndns-pics.com,dyndns-remote.com,dyndns-server.com,dyndns-web.com,dyndns-wiki.com,dyndns-work.com,dyndns.biz,dyndns.ddnss.de,dyndns.info,dyndns.org,dyndns.tv,dyndns.ws,dyndns1.de,dynns.com,dynserv.org,dynu.net,dynv6.net,dynvpn.de,dyroy.no,dyrøy.no,dz,dønna.no,e.bg,e.se,e12.ve,e164.arpa,e4.cz,earth,east-kazakhstan.su,eastafrica.museum,eastcoast.museum,eat,eating-organic.net,eaton.mi.us,ebetsu.hokkaido.jp,ebina.kanagawa.jp,ebino.miyazaki.jp,ebiz.tw,ec,echizen.fukui.jp,ecn.br,eco,eco.br,ecologia.bo,economia.bo,ed.ao,ed.ci,ed.cr,ed.jp,ed.pw,edeka,edogawa.tokyo.jp,edu,edu.ac,edu.af,edu.al,edu.ar,edu.au,edu.az,edu.ba,edu.bb,edu.bh,edu.bi,edu.bm,edu.bn,edu.bo,edu.br,edu.bs,edu.bt,edu.bz,edu.ci,edu.cn,edu.co,edu.cu,edu.cw,edu.dm,edu.do,edu.dz,edu.ec,edu.ee,edu.eg,edu.es,edu.et,edu.eu.org,edu.ge,edu.gh,edu.gi,edu.gl,edu.gn,edu.gp,edu.gr,edu.gt,edu.gu,edu.gy,edu.hk,edu.hn,edu.ht,edu.in,edu.iq,edu.is,edu.it,edu.jo,edu.kg,edu.ki,edu.km,edu.kn,edu.kp,edu.krd,edu.kw,edu.ky,edu.kz,edu.la,edu.lb,edu.lc,edu.lk,edu.lr,edu.ls,edu.lv,edu.ly,edu.me,edu.mg,edu.mk,edu.ml,edu.mn,edu.mo,edu.ms,edu.mt,edu.mv,edu.mw,edu.mx,edu.my,edu.mz,edu.ng,edu.ni,edu.nr,edu.om,edu.pa,edu.pe,edu.pf,edu.ph,edu.pk,edu.pl,edu.pn,edu.pr,edu.ps,edu.pt,edu.py,edu.qa,edu.rs,edu.ru,edu.sa,edu.sb,edu.sc,edu.sd,edu.sg,edu.sl,edu.sn,edu.st,edu.sv,edu.sy,edu.tj,edu.tm,edu.to,edu.tr,edu.tt,edu.tw,edu.ua,edu.uy,edu.vc,edu.ve,edu.vn,edu.vu,edu.ws,edu.za,edu.zm,education,education.museum,educational.museum,educator.aero,edugit.org,edunet.tn,ee,ee.eu.org,eg,egersund.no,egyptian.museum,ehime.jp,eid.no,eidfjord.no,eidsberg.no,eidskog.no,eidsvoll.no,eigersund.no,eiheiji.fukui.jp,eisenbahn.museum,ekloges.cy,elasticbeanstalk.com,elb.amazonaws.com,elb.amazonaws.com.cn,elblag.pl,elburg.museum,elk.pl,elvendrell.museum,elverum.no,email,emb.kw,embaixada.st,embetsu.hokkaido.jp,embroidery.museum,emerck,emergency.aero,emilia-romagna.it,emiliaromagna.it,emp.br,empresa.bo,emr.it,en.it,ena.gifu.jp,encyclopedic.museum,endofinternet.net,endofinternet.org,endoftheinternet.org,enebakk.no,energy,eng.br,eng.pro,engerdal.no,engine.aero,engineer,engineer.aero,engineering,england.museum,eniwa.hokkaido.jp,enna.it,enonic.io,ens.tn,enterprisecloud.nu,enterprises,entertainment.aero,entomology.museum,environment.museum,environmentalconservation.museum,epilepsy.museum,epson,equipment,equipment.aero,er,ericsson,erimo.hokkaido.jp,erni,erotica.hu,erotika.hu,es,es.eu.org,es.gov.br,es.kr,es.leg.br,esan.hokkaido.jp,esashi.hokkaido.jp,esp.br,esq,essex.museum,est-a-la-maison.com,est-a-la-masion.com,est-le-patron.com,est-mon-blogueur.com,est.pr,estate,estate.museum,esurance,et,etajima.hiroshima.jp,etc.br,ethnology.museum,eti.br,etisalat,etne.no,etnedal.no,eu,eu-1.evennode.com,eu-2.evennode.com,eu-3.evennode.com,eu-4.evennode.com,eu-central-1.elasticbeanstalk.com,eu-west-1.elasticbeanstalk.com,eu-west-2.elasticbeanstalk.com,eu-west-3.elasticbeanstalk.com,eu.com,eu.int,eu.meteorapp.com,eu.org,eun.eg,eurovision,eus,evenassi.no,evenes.no,events,evenášši.no,everbank,evje-og-hornnes.no,ex.futurecms.at,ex.ortsinfo.at,exchange,exchange.aero,exeter.museum,exhibition.museum,exnet.su,expert,experts-comptables.fr,exposed,express,express.aero,extraspace,f.bg,f.se,fage,fail,fairwinds,faith,fam.pk,family,family.museum,familyds.com,familyds.net,familyds.org,fan,fans,fantasyleague.cc,far.br,farm,farm.museum,farmequipment.museum,farmers,farmers.museum,farmstead.museum,farsund.no,fashion,fast,fastly-terrarium.com,fastlylb.net,fastpanel.direct,fastvps-server.com,fauske.no,fbx-os.fr,fbxos.fr,fc.it,fe.it,fed.us,federation.aero,fedex,fedje.no,fedorainfracloud.org,fedorapeople.org,feedback,feira.br,fermo.it,ferrara.it,ferrari,ferrero,feste-ip.net,fet.no,fetsund.no,fg.it,fh.se,fhapp.xyz,fhs.no,fhsk.se,fhv.se,fi,fi.cr,fi.eu.org,fi.it,fiat,fidelity,fido,fie.ee,field.museum,figueres.museum,filatelia.museum,filegear-au.me,filegear-de.me,filegear-gb.me,filegear-ie.me,filegear-jp.me,filegear-sg.me,filegear.me,film,film.hu,film.museum,fin.ci,fin.ec,fin.tn,final,finance,financial,fineart.museum,finearts.museum,finland.museum,finnoy.no,finnøy.no,fire,firebaseapp.com,firenze.it,firestone,firewall-gateway.com,firewall-gateway.de,firewall-gateway.net,firm.co,firm.dk,firm.ht,firm.in,firm.nf,firm.ng,firm.ro,firm.ve,firmdale,fish,fishing,fit,fitjar.no,fitness,fj,fj.cn,fjaler.no,fjell.no,fk,fl.us,fla.no,flakstad.no,flanders.museum,flatanger.no,flekkefjord.no,flesberg.no,flickr,flight.aero,flights,flir,flog.br,flora.no,florence.it,florida.museum,floripa.br,florist,floro.no,florø.no,flowers,flt.cloud.muni.cz,fly,flynnhosting.net,flynnhub.com,flå.no,fm,fm.br,fm.it,fm.no,fnd.br,fo,foggia.it,folkebibl.no,folldal.no,foo,food,foodnetwork,football,for-better.biz,for-more.biz,for-our.info,for-some.biz,for-the.biz,for.men,for.mom,for.one,for.sale,force.museum,ford,forde.no,forex,forgot.her.name,forgot.his.name,forli-cesena.it,forlicesena.it,forlì-cesena.it,forlìcesena.it,forsale,forsand.no,fortal.br,fortmissoula.museum,fortworth.museum,forum,forum.hu,forumz.info,fosnes.no,fot.br,foundation,foundation.museum,fox,foz.br,fr,fr.eu.org,fr.it,frana.no,francaise.museum,frankfurt.museum,franziskaner.museum,fredrikstad.no,free,free.hr,freebox-os.com,freebox-os.fr,freeboxos.com,freeboxos.fr,freeddns.org,freeddns.us,freedesktop.org,freemasonry.museum,freesite.host,freetls.fastly.net,frei.no,freiburg.museum,freight.aero,fresenius,fribourg.museum,friuli-v-giulia.it,friuli-ve-giulia.it,friuli-vegiulia.it,friuli-venezia-giulia.it,friuli-veneziagiulia.it,friuli-vgiulia.it,friuliv-giulia.it,friulive-giulia.it,friulivegiulia.it,friulivenezia-giulia.it,friuliveneziagiulia.it,friulivgiulia.it,frl,frog.museum,frogans,frogn.no,froland.no,from-ak.com,from-al.com,from-ar.com,from-az.net,from-ca.com,from-co.net,from-ct.com,from-dc.com,from-de.com,from-fl.com,from-ga.com,from-hi.com,from-ia.com,from-id.com,from-il.com,from-in.com,from-ks.com,from-ky.com,from-la.net,from-ma.com,from-md.com,from-me.org,from-mi.com,from-mn.com,from-mo.com,from-ms.com,from-mt.com,from-nc.com,from-nd.com,from-ne.com,from-nh.com,from-nj.com,from-nm.com,from-nv.com,from-ny.net,from-oh.com,from-ok.com,from-or.com,from-pa.com,from-pr.com,from-ri.com,from-sc.com,from-sd.com,from-tn.com,from-tx.com,from-ut.com,from-va.com,from-vt.com,from-wa.com,from-wi.com,from-wv.com,from-wy.com,from.hr,frontdoor,frontier,frosinone.it,frosta.no,froya.no,fræna.no,frøya.no,fst.br,ftpaccess.cc,ftr,fuchu.hiroshima.jp,fuchu.tokyo.jp,fuchu.toyama.jp,fudai.iwate.jp,fuefuki.yamanashi.jp,fuel.aero,fuettertdasnetz.de,fuji.shizuoka.jp,fujieda.shizuoka.jp,fujiidera.osaka.jp,fujikawa.shizuoka.jp,fujikawa.yamanashi.jp,fujikawaguchiko.yamanashi.jp,fujimi.nagano.jp,fujimi.saitama.jp,fujimino.saitama.jp,fujinomiya.shizuoka.jp,fujioka.gunma.jp,fujisato.akita.jp,fujisawa.iwate.jp,fujisawa.kanagawa.jp,fujishiro.ibaraki.jp,fujitsu,fujixerox,fujiyoshida.yamanashi.jp,fukagawa.hokkaido.jp,fukaya.saitama.jp,fukuchi.fukuoka.jp,fukuchiyama.kyoto.jp,fukudomi.saga.jp,fukui.fukui.jp,fukui.jp,fukumitsu.toyama.jp,fukuoka.jp,fukuroi.shizuoka.jp,fukusaki.hyogo.jp,fukushima.fukushima.jp,fukushima.hokkaido.jp,fukushima.jp,fukuyama.hiroshima.jp,fun,funabashi.chiba.jp,funagata.yamagata.jp,funahashi.toyama.jp,fund,fundacio.museum,fuoisku.no,fuossko.no,furano.hokkaido.jp,furniture,furniture.museum,furubira.hokkaido.jp,furudono.fukushima.jp,furukawa.miyagi.jp,fusa.no,fuso.aichi.jp,fussa.tokyo.jp,futaba.fukushima.jp,futbol,futsu.nagasaki.jp,futtsu.chiba.jp,futurecms.at,futurehosting.at,futuremailing.at,fvg.it,fyi,fylkesbibl.no,fyresdal.no,førde.no,g.bg,g.se,g12.br,ga,ga.us,gaivuotna.no,gal,gallery,gallery.museum,gallo,gallup,galsa.no,gamagori.aichi.jp,game,game-host.org,game-server.cc,game.tw,games,games.hu,gamo.shiga.jp,gamvik.no,gangaviika.no,gangwon.kr,gap,garden,garden.museum,gateway.museum,gaular.no,gausdal.no,gay,gb,gb.com,gb.net,gbiz,gc.ca,gd,gd.cn,gda.pl,gdansk.pl,gdn,gdynia.pl,ge,ge.it,gea,geek.nz,geekgalaxy.com,geelvinck.museum,gehirn.ne.jp,geisei.kochi.jp,gemological.museum,gen.in,gen.mi.us,gen.ng,gen.nz,gen.tr,genkai.saga.jp,genoa.it,genova.it,gent,genting,geology.museum,geometre-expert.fr,george,georgia.museum,georgia.su,getmyip.com,gets-it.net,gf,gg,ggee,ggf.br,gh,gi,giehtavuoatna.no,giessen.museum,gift,gifts,gifu.gifu.jp,gifu.jp,giize.com,gildeskal.no,gildeskål.no,ginan.gifu.jp,ginowan.okinawa.jp,ginoza.okinawa.jp,giske.no,git-pages.rit.edu,git-repos.de,github.io,githubusercontent.com,gitlab.io,gives,giving,gjemnes.no,gjerdrum.no,gjerstad.no,gjesdal.no,gjovik.no,gjøvik.no,gl,glade,glas.museum,glass,glass.museum,gle,gleeze.com,gliding.aero,glitch.me,gliwice.pl,global,global.prod.fastly.net,global.ssl.fastly.net,globo,glogow.pl,gloppen.no,glug.org.uk,gm,gmail,gmbh,gmina.pl,gmo,gmx,gn,gniezno.pl,go-vip.co,go-vip.net,go.ci,go.cr,go.dyndns.org,go.gov.br,go.id,go.it,go.jp,go.ke,go.kr,go.leg.br,go.pw,go.th,go.tj,go.tz,go.ug,gob.ar,gob.bo,gob.cl,gob.do,gob.ec,gob.es,gob.gt,gob.hn,gob.mx,gob.ni,gob.pa,gob.pe,gob.pk,gob.sv,gob.ve,gobo.wakayama.jp,godaddy,godo.gifu.jp,goiania.br,goip.de,gojome.akita.jp,gok.pk,gokase.miyazaki.jp,gol.no,gold,goldpoint,golf,golffan.us,gon.pk,gonohe.aomori.jp,goo,goodyear,goog,google,googleapis.com,googlecode.com,gop,gop.pk,gorge.museum,gorizia.it,gorlice.pl,gos.pk,gose.nara.jp,gosen.niigata.jp,goshiki.hyogo.jp,got,gotdns.ch,gotdns.com,gotdns.org,gotemba.shizuoka.jp,goto.nagasaki.jp,gotpantheon.com,gotsu.shimane.jp,gouv.bj,gouv.ci,gouv.fr,gouv.ht,gouv.km,gouv.ml,gouv.sn,gov,gov.ac,gov.ae,gov.af,gov.al,gov.ar,gov.as,gov.au,gov.az,gov.ba,gov.bb,gov.bf,gov.bh,gov.bm,gov.bn,gov.br,gov.bs,gov.bt,gov.by,gov.bz,gov.cd,gov.cl,gov.cm,gov.cn,gov.co,gov.cu,gov.cx,gov.cy,gov.dm,gov.do,gov.dz,gov.ec,gov.ee,gov.eg,gov.et,gov.ge,gov.gh,gov.gi,gov.gn,gov.gr,gov.gu,gov.gy,gov.hk,gov.ie,gov.il,gov.in,gov.iq,gov.ir,gov.is,gov.it,gov.jo,gov.kg,gov.ki,gov.km,gov.kn,gov.kp,gov.kw,gov.ky,gov.kz,gov.la,gov.lb,gov.lc,gov.lk,gov.lr,gov.ls,gov.lt,gov.lv,gov.ly,gov.ma,gov.me,gov.mg,gov.mk,gov.ml,gov.mn,gov.mo,gov.mr,gov.ms,gov.mu,gov.mv,gov.mw,gov.my,gov.mz,gov.nc.tr,gov.ng,gov.nr,gov.om,gov.ph,gov.pk,gov.pl,gov.pn,gov.pr,gov.ps,gov.pt,gov.py,gov.qa,gov.rs,gov.ru,gov.rw,gov.sa,gov.sb,gov.sc,gov.sd,gov.sg,gov.sh,gov.sl,gov.st,gov.sx,gov.sy,gov.tj,gov.tl,gov.tm,gov.tn,gov.to,gov.tr,gov.tt,gov.tw,gov.ua,gov.uk,gov.vc,gov.ve,gov.vn,gov.ws,gov.za,gov.zm,gov.zw,government.aero,govt.nz,gp,gq,gr,gr.com,gr.eu.org,gr.it,gr.jp,grainger,grajewo.pl,gran.no,grandrapids.museum,grane.no,granvin.no,graphics,gratangen.no,gratis,graz.museum,green,greta.fr,grimstad.no,gripe,griw.gov.pl,grocery,groks-the.info,groks-this.info,grondar.za,grong.no,grosseto.it,groundhandling.aero,group,group.aero,grozny.ru,grozny.su,grp.lk,gru.br,grue.no,gs,gs.aa.no,gs.ah.no,gs.bu.no,gs.cn,gs.fm.no,gs.hl.no,gs.hm.no,gs.jan-mayen.no,gs.mr.no,gs.nl.no,gs.nt.no,gs.of.no,gs.ol.no,gs.oslo.no,gs.rl.no,gs.sf.no,gs.st.no,gs.svalbard.no,gs.tm.no,gs.tr.no,gs.va.no,gs.vf.no,gsm.pl,gt,gu,gu.us,guam.gu,guardian,gub.uy,gucci,guernsey.museum,guge,guide,guitars,gujo.gifu.jp,gulen.no,gunma.jp,guovdageaidnu.no,guru,gushikami.okinawa.jp,gv.ao,gv.at,gv.vc,gw,gwangju.kr,gwiddle.co.uk,gx.cn,gy,gyeongbuk.kr,gyeonggi.kr,gyeongnam.kr,gyokuto.kumamoto.jp,gz.cn,gáivuotna.no,gálsá.no,gáŋgaviika.no,h.bg,h.se,ha.cn,ha.no,habikino.osaka.jp,habmer.no,haboro.hokkaido.jp,hachijo.tokyo.jp,hachinohe.aomori.jp,hachioji.tokyo.jp,hachirogata.akita.jp,hadano.kanagawa.jp,hadsel.no,haebaru.okinawa.jp,haga.tochigi.jp,hagebostad.no,hagi.yamaguchi.jp,haibara.shizuoka.jp,hair,hakata.fukuoka.jp,hakodate.hokkaido.jp,hakone.kanagawa.jp,hakuba.nagano.jp,hakui.ishikawa.jp,hakusan.ishikawa.jp,halden.no,half.host,halloffame.museum,halsa.no,ham-radio-op.net,hamada.shimane.jp,hamamatsu.shizuoka.jp,hamar.no,hamaroy.no,hamatama.saga.jp,hamatonbetsu.hokkaido.jp,hamburg,hamburg.museum,hammarfeasta.no,hammerfest.no,hamura.tokyo.jp,hanamaki.iwate.jp,hanamigawa.chiba.jp,hanawa.fukushima.jp,handa.aichi.jp,handson.museum,hanggliding.aero,hangout,hannan.osaka.jp,hanno.saitama.jp,hanyu.saitama.jp,hapmir.no,happou.akita.jp,hara.nagano.jp,haram.no,hareid.no,harima.hyogo.jp,harstad.no,harvestcelebration.museum,hasama.oita.jp,hasami.nagasaki.jp,hashbang.sh,hashikami.aomori.jp,hashima.gifu.jp,hashimoto.wakayama.jp,hasuda.saitama.jp,hasura-app.io,hasura.app,hasvik.no,hatogaya.saitama.jp,hatoyama.saitama.jp,hatsukaichi.hiroshima.jp,hattfjelldal.no,haugesund.no,haus,hawaii.museum,hayakawa.yamanashi.jp,hayashima.okayama.jp,hazu.aichi.jp,hb.cldmail.ru,hb.cn,hbo,hdfc,hdfcbank,he.cn,health,health-carereform.com,health.museum,health.nz,health.vn,healthcare,heguri.nara.jp,heimatunduhren.museum,hekinan.aichi.jp,hellas.museum,help,helsinki,helsinki.museum,hembygdsforbund.museum,hemne.no,hemnes.no,hemsedal.no,hepforge.org,herad.no,here,here-for-more.info,heritage.museum,hermes,herokuapp.com,herokussl.com,heroy.more-og-romsdal.no,heroy.nordland.no,herøy.møre-og-romsdal.no,herøy.nordland.no,hgtv,hi.cn,hi.us,hicam.net,hichiso.gifu.jp,hida.gifu.jp,hidaka.hokkaido.jp,hidaka.kochi.jp,hidaka.saitama.jp,hidaka.wakayama.jp,higashi.fukuoka.jp,higashi.fukushima.jp,higashi.okinawa.jp,higashiagatsuma.gunma.jp,higashichichibu.saitama.jp,higashihiroshima.hiroshima.jp,higashiizu.shizuoka.jp,higashiizumo.shimane.jp,higashikagawa.kagawa.jp,higashikagura.hokkaido.jp,higashikawa.hokkaido.jp,higashikurume.tokyo.jp,higashimatsushima.miyagi.jp,higashimatsuyama.saitama.jp,higashimurayama.tokyo.jp,higashinaruse.akita.jp,higashine.yamagata.jp,higashiomi.shiga.jp,higashiosaka.osaka.jp,higashishirakawa.gifu.jp,higashisumiyoshi.osaka.jp,higashitsuno.kochi.jp,higashiura.aichi.jp,higashiyama.kyoto.jp,higashiyamato.tokyo.jp,higashiyodogawa.osaka.jp,higashiyoshino.nara.jp,hiji.oita.jp,hikari.yamaguchi.jp,hikawa.shimane.jp,hikimi.shimane.jp,hikone.shiga.jp,himeji.hyogo.jp,himeshima.oita.jp,himi.toyama.jp,hino.tokyo.jp,hino.tottori.jp,hinode.tokyo.jp,hinohara.tokyo.jp,hioki.kagoshima.jp,hiphop,hirado.nagasaki.jp,hiraizumi.iwate.jp,hirakata.osaka.jp,hiranai.aomori.jp,hirara.okinawa.jp,hirata.fukushima.jp,hiratsuka.kanagawa.jp,hiraya.nagano.jp,hirogawa.wakayama.jp,hirokawa.fukuoka.jp,hirono.fukushima.jp,hirono.iwate.jp,hiroo.hokkaido.jp,hirosaki.aomori.jp,hiroshima.jp,hisamitsu,hisayama.fukuoka.jp,histoire.museum,historical.museum,historicalsociety.museum,historichouses.museum,historisch.museum,historisches.museum,history.museum,historyofscience.museum,hita.oita.jp,hitachi,hitachi.ibaraki.jp,hitachinaka.ibaraki.jp,hitachiomiya.ibaraki.jp,hitachiota.ibaraki.jp,hitra.no,hiv,hizen.saga.jp,hjartdal.no,hjelmeland.no,hk,hk.cn,hk.com,hk.org,hkt,hl.cn,hl.no,hm,hm.no,hn,hn.cn,hobby-site.com,hobby-site.org,hobol.no,hobøl.no,hockey,hof.no,hofu.yamaguchi.jp,hokkaido.jp,hokksund.no,hokuryu.hokkaido.jp,hokuto.hokkaido.jp,hokuto.yamanashi.jp,hol.no,holdings,hole.no,holiday,holmestrand.no,holtalen.no,holtålen.no,home-webserver.de,home.dyndns.org,homebuilt.aero,homedepot,homedns.org,homeftp.net,homeftp.org,homegoods,homeip.net,homelink.one,homelinux.com,homelinux.net,homelinux.org,homeoffice.gov.uk,homes,homesecuritymac.com,homesecuritypc.com,homesense,homeunix.com,homeunix.net,homeunix.org,honai.ehime.jp,honbetsu.hokkaido.jp,honda,honefoss.no,honeywell,hongo.hiroshima.jp,honjo.akita.jp,honjo.saitama.jp,honjyo.akita.jp,hopto.me,hopto.org,hornindal.no,horokanai.hokkaido.jp,horology.museum,horonobe.hokkaido.jp,horse,horten.no,hospital,host,hosting,hosting-cluster.nl,hosting.myjino.ru,hot,hotel.hu,hotel.lk,hotel.tz,hoteles,hotels,hotmail,house,house.museum,how,hoyanger.no,hoylandet.no,hr,hr.eu.org,hs.kr,hs.run,hs.zone,hsbc,ht,hu,hu.com,hu.eu.org,hu.net,hughes,huissier-justice.fr,humanities.museum,hurdal.no,hurum.no,hvaler.no,hyatt,hyllestad.no,hyogo.jp,hyuga.miyazaki.jp,hyundai,hzc.io,hábmer.no,hámmárfeasta.no,hápmir.no,häkkinen.fi,hå.no,hægebostad.no,hønefoss.no,høyanger.no,høylandet.no,i.bg,i.ng,i.ph,i.se,i234.me,ia.us,iamallama.com,ibara.okayama.jp,ibaraki.ibaraki.jp,ibaraki.jp,ibaraki.osaka.jp,ibestad.no,ibigawa.gifu.jp,ibm,ic.gov.pl,icbc,ice,ichiba.tokushima.jp,ichihara.chiba.jp,ichikai.tochigi.jp,ichikawa.chiba.jp,ichikawa.hyogo.jp,ichikawamisato.yamanashi.jp,ichinohe.iwate.jp,ichinomiya.aichi.jp,ichinomiya.chiba.jp,ichinoseki.iwate.jp,icu,id,id.au,id.ir,id.lv,id.ly,id.us,ide.kyoto.jp,idf.il,idrett.no,idv.hk,idv.tw,ie,ie.eu.org,ieee,if.ua,ifm,iglesias-carbonia.it,iglesiascarbonia.it,iheya.okinawa.jp,iida.nagano.jp,iide.yamagata.jp,iijima.nagano.jp,iitate.fukushima.jp,iiyama.nagano.jp,iizuka.fukuoka.jp,iizuna.nagano.jp,ikano,ikaruga.nara.jp,ikata.ehime.jp,ikawa.akita.jp,ikeda.fukui.jp,ikeda.gifu.jp,ikeda.hokkaido.jp,ikeda.nagano.jp,ikeda.osaka.jp,iki.fi,iki.nagasaki.jp,ikoma.nara.jp,ikusaka.nagano.jp,il,il.eu.org,il.us,ilawa.pl,illustration.museum,ilovecollege.info,im,im.it,imabari.ehime.jp,imageandsound.museum,imakane.hokkaido.jp,imamat,imari.saga.jp,imb.br,imdb,imizu.toyama.jp,immo,immobilien,imperia.it,in,in-addr.arpa,in-berlin.de,in-brb.de,in-butter.de,in-dsl.de,in-dsl.net,in-dsl.org,in-the-band.net,in-vpn.de,in-vpn.net,in-vpn.org,in.eu.org,in.futurecms.at,in.london,in.na,in.net,in.ni,in.rs,in.th,in.ua,in.us,ina.ibaraki.jp,ina.nagano.jp,ina.saitama.jp,inabe.mie.jp,inagawa.hyogo.jp,inagi.tokyo.jp,inami.toyama.jp,inami.wakayama.jp,inashiki.ibaraki.jp,inatsuki.fukuoka.jp,inawashiro.fukushima.jp,inazawa.aichi.jp,inc,inc.hk,incheon.kr,ind.br,ind.gt,ind.in,ind.kw,ind.tn,inderoy.no,inderøy.no,indian.museum,indiana.museum,indianapolis.museum,indianmarket.museum,indigena.bo,industria.bo,industries,ine.kyoto.jp,inf.br,inf.cu,inf.mk,inf.ua,infiniti,info,info.at,info.au,info.az,info.bb,info.bo,info.co,info.cx,info.ec,info.et,info.gu,info.ht,info.hu,info.ke,info.ki,info.la,info.ls,info.mv,info.na,info.nf,info.ni,info.nr,info.pk,info.pl,info.pr,info.ro,info.sd,info.tn,info.tr,info.tt,info.tz,info.ve,info.vn,info.zm,ing,ing.pa,ingatlan.hu,ink,ino.kochi.jp,instantcloud.cn,institute,insurance,insurance.aero,insure,int,int.ar,int.az,int.bo,int.ci,int.co,int.eu.org,int.is,int.la,int.lk,int.mv,int.mw,int.ni,int.pt,int.ru,int.tj,int.tt,int.ve,int.vn,intel,intelligence.museum,interactive.museum,international,internet-dns.de,intl.tn,intuit,inuyama.aichi.jp,investments,inzai.chiba.jp,io,iobb.net,ip6.arpa,ipifony.net,ipiranga,iq,ir,iraq.museum,iris.arpa,irish,iron.museum,iruma.saitama.jp,is,is-a-anarchist.com,is-a-blogger.com,is-a-bookkeeper.com,is-a-bruinsfan.org,is-a-bulls-fan.com,is-a-candidate.org,is-a-caterer.com,is-a-celticsfan.org,is-a-chef.com,is-a-chef.net,is-a-chef.org,is-a-conservative.com,is-a-cpa.com,is-a-cubicle-slave.com,is-a-democrat.com,is-a-designer.com,is-a-doctor.com,is-a-financialadvisor.com,is-a-geek.com,is-a-geek.net,is-a-geek.org,is-a-green.com,is-a-guru.com,is-a-hard-worker.com,is-a-hunter.com,is-a-knight.org,is-a-landscaper.com,is-a-lawyer.com,is-a-liberal.com,is-a-libertarian.com,is-a-linux-user.org,is-a-llama.com,is-a-musician.com,is-a-nascarfan.com,is-a-nurse.com,is-a-painter.com,is-a-patsfan.org,is-a-personaltrainer.com,is-a-photographer.com,is-a-player.com,is-a-republican.com,is-a-rockstar.com,is-a-socialist.com,is-a-soxfan.org,is-a-student.com,is-a-teacher.com,is-a-techie.com,is-a-therapist.com,is-an-accountant.com,is-an-actor.com,is-an-actress.com,is-an-anarchist.com,is-an-artist.com,is-an-engineer.com,is-an-entertainer.com,is-by.us,is-certified.com,is-found.org,is-gone.com,is-into-anime.com,is-into-cars.com,is-into-cartoons.com,is-into-games.com,is-leet.com,is-lost.org,is-not-certified.com,is-saved.org,is-slick.com,is-uberleet.com,is-very-bad.org,is-very-evil.org,is-very-good.org,is-very-nice.org,is-very-sweet.org,is-with-theband.com,is.eu.org,is.gov.pl,is.it,isa-geek.com,isa-geek.net,isa-geek.org,isa-hockeynut.com,isa.kagoshima.jp,isa.us,isahaya.nagasaki.jp,ise.mie.jp,isehara.kanagawa.jp,iselect,isen.kagoshima.jp,isernia.it,iserv.dev,isesaki.gunma.jp,ishigaki.okinawa.jp,ishikari.hokkaido.jp,ishikawa.fukushima.jp,ishikawa.jp,ishikawa.okinawa.jp,ishinomaki.miyagi.jp,isla.pr,isleofman.museum,ismaili,isshiki.aichi.jp,issmarterthanyou.com,ist,istanbul,isteingeek.de,istmein.de,isumi.chiba.jp,it,it.ao,it.eu.org,itabashi.tokyo.jp,itako.ibaraki.jp,itakura.gunma.jp,itami.hyogo.jp,itano.tokushima.jp,itau,itayanagi.aomori.jp,ito.shizuoka.jp,itoigawa.niigata.jp,itoman.okinawa.jp,its.me,itv,ivano-frankivsk.ua,ivanovo.su,iveco,iveland.no,ivgu.no,iwade.wakayama.jp,iwafune.tochigi.jp,iwaizumi.iwate.jp,iwaki.fukushima.jp,iwakuni.yamaguchi.jp,iwakura.aichi.jp,iwama.ibaraki.jp,iwamizawa.hokkaido.jp,iwanai.hokkaido.jp,iwanuma.miyagi.jp,iwata.shizuoka.jp,iwate.iwate.jp,iwate.jp,iwatsuki.saitama.jp,iwi.nz,iyo.ehime.jp,iz.hr,izena.okinawa.jp,izu.shizuoka.jp,izumi.kagoshima.jp,izumi.osaka.jp,izumiotsu.osaka.jp,izumisano.osaka.jp,izumizaki.fukushima.jp,izumo.shimane.jp,izumozaki.niigata.jp,izunokuni.shizuoka.jp,j.bg,jab.br,jaguar,jambyl.su,jamison.museum,jampa.br,jan-mayen.no,java,jaworzno.pl,jcb,jcp,jdevcloud.com,jdf.br,je,jeep,jefferson.museum,jeju.kr,jelenia-gora.pl,jeonbuk.kr,jeonnam.kr,jerusalem.museum,jessheim.no,jetzt,jevnaker.no,jewelry,jewelry.museum,jewish.museum,jewishart.museum,jfk.museum,jgora.pl,jinsekikogen.hiroshima.jp,jio,jl.cn,jll,jm,jmp,jnj,jo,joboji.iwate.jp,jobs,jobs.tt,joburg,joetsu.niigata.jp,jogasz.hu,johana.toyama.jp,joinville.br,jolster.no,jondal.no,jor.br,jorpeland.no,joso.ibaraki.jp,jot,journal.aero,journalism.museum,journalist.aero,joy,joyo.kyoto.jp,jp,jp.eu.org,jp.net,jpmorgan,jpn.com,jprs,js.cn,js.org,judaica.museum,judygarland.museum,juedisches.museum,juegos,juif.museum,juniper,jur.pro,jus.br,jx.cn,jølster.no,jørpeland.no,k.bg,k.se,k12.ak.us,k12.al.us,k12.ar.us,k12.as.us,k12.az.us,k12.ca.us,k12.co.us,k12.ct.us,k12.dc.us,k12.de.us,k12.ec,k12.fl.us,k12.ga.us,k12.gu.us,k12.ia.us,k12.id.us,k12.il,k12.il.us,k12.in.us,k12.ks.us,k12.ky.us,k12.la.us,k12.ma.us,k12.md.us,k12.me.us,k12.mi.us,k12.mn.us,k12.mo.us,k12.ms.us,k12.mt.us,k12.nc.us,k12.ne.us,k12.nh.us,k12.nj.us,k12.nm.us,k12.nv.us,k12.ny.us,k12.oh.us,k12.ok.us,k12.or.us,k12.pa.us,k12.pr.us,k12.ri.us,k12.sc.us,k12.tn.us,k12.tr,k12.tx.us,k12.ut.us,k12.va.us,k12.vi,k12.vi.us,k12.vt.us,k12.wa.us,k12.wi.us,k12.wy.us,kaas.gg,kadena.okinawa.jp,kadogawa.miyazaki.jp,kadoma.osaka.jp,kafjord.no,kaga.ishikawa.jp,kagami.kochi.jp,kagamiishi.fukushima.jp,kagamino.okayama.jp,kagawa.jp,kagoshima.jp,kagoshima.kagoshima.jp,kaho.fukuoka.jp,kahoku.ishikawa.jp,kahoku.yamagata.jp,kai.yamanashi.jp,kainan.tokushima.jp,kainan.wakayama.jp,kaisei.kanagawa.jp,kaita.hiroshima.jp,kaizuka.osaka.jp,kakamigahara.gifu.jp,kakegawa.shizuoka.jp,kakinoki.shimane.jp,kakogawa.hyogo.jp,kakuda.miyagi.jp,kalisz.pl,kalmykia.ru,kalmykia.su,kaluga.su,kamagaya.chiba.jp,kamaishi.iwate.jp,kamakura.kanagawa.jp,kameoka.kyoto.jp,kameyama.mie.jp,kami.kochi.jp,kami.miyagi.jp,kamiamakusa.kumamoto.jp,kamifurano.hokkaido.jp,kamigori.hyogo.jp,kamiichi.toyama.jp,kamiizumi.saitama.jp,kamijima.ehime.jp,kamikawa.hokkaido.jp,kamikawa.hyogo.jp,kamikawa.saitama.jp,kamikitayama.nara.jp,kamikoani.akita.jp,kamimine.saga.jp,kaminokawa.tochigi.jp,kaminoyama.yamagata.jp,kamioka.akita.jp,kamisato.saitama.jp,kamishihoro.hokkaido.jp,kamisu.ibaraki.jp,kamisunagawa.hokkaido.jp,kamitonda.wakayama.jp,kamitsue.oita.jp,kamo.kyoto.jp,kamo.niigata.jp,kamoenai.hokkaido.jp,kamogawa.chiba.jp,kanagawa.jp,kanan.osaka.jp,kanazawa.ishikawa.jp,kanegasaki.iwate.jp,kaneyama.fukushima.jp,kaneyama.yamagata.jp,kani.gifu.jp,kanie.aichi.jp,kanmaki.nara.jp,kanna.gunma.jp,kannami.shizuoka.jp,kanonji.kagawa.jp,kanoya.kagoshima.jp,kanra.gunma.jp,kanuma.tochigi.jp,kanzaki.saga.jp,karacol.su,karaganda.su,karasjohka.no,karasjok.no,karasuyama.tochigi.jp,karate.museum,karatsu.saga.jp,karelia.su,karikatur.museum,kariwa.niigata.jp,kariya.aichi.jp,karlsoy.no,karmoy.no,karmøy.no,karpacz.pl,kartuzy.pl,karuizawa.nagano.jp,karumai.iwate.jp,kasahara.gifu.jp,kasai.hyogo.jp,kasama.ibaraki.jp,kasamatsu.gifu.jp,kasaoka.okayama.jp,kashiba.nara.jp,kashihara.nara.jp,kashima.ibaraki.jp,kashima.saga.jp,kashiwa.chiba.jp,kashiwara.osaka.jp,kashiwazaki.niigata.jp,kasuga.fukuoka.jp,kasuga.hyogo.jp,kasugai.aichi.jp,kasukabe.saitama.jp,kasumigaura.ibaraki.jp,kasuya.fukuoka.jp,kaszuby.pl,katagami.akita.jp,katano.osaka.jp,katashina.gunma.jp,katori.chiba.jp,katowice.pl,katsuragi.nara.jp,katsuragi.wakayama.jp,katsushika.tokyo.jp,katsuura.chiba.jp,katsuyama.fukui.jp,kaufen,kautokeino.no,kawaba.gunma.jp,kawachinagano.osaka.jp,kawagoe.mie.jp,kawagoe.saitama.jp,kawaguchi.saitama.jp,kawahara.tottori.jp,kawai.iwate.jp,kawai.nara.jp,kawajima.saitama.jp,kawakami.nagano.jp,kawakami.nara.jp,kawakita.ishikawa.jp,kawamata.fukushima.jp,kawaminami.miyazaki.jp,kawanabe.kagoshima.jp,kawanehon.shizuoka.jp,kawanishi.hyogo.jp,kawanishi.nara.jp,kawanishi.yamagata.jp,kawara.fukuoka.jp,kawasaki.jp,kawasaki.miyagi.jp,kawatana.nagasaki.jp,kawaue.gifu.jp,kawazu.shizuoka.jp,kayabe.hokkaido.jp,kazimierz-dolny.pl,kazo.saitama.jp,kazuno.akita.jp,kddi,ke,keisen.fukuoka.jp,kembuchi.hokkaido.jp,kep.tr,kepno.pl,kerryhotels,kerrylogistics,kerryproperties,ketrzyn.pl,keymachine.de,kfh,kg,kg.kr,kh,kh.ua,khakassia.su,kharkiv.ua,kharkov.ua,kherson.ua,khmelnitskiy.ua,khmelnytskyi.ua,khplay.nl,ki,kia,kibichuo.okayama.jp,kicks-ass.net,kicks-ass.org,kids.museum,kids.us,kiev.ua,kiho.mie.jp,kihoku.ehime.jp,kijo.miyazaki.jp,kikonai.hokkaido.jp,kikuchi.kumamoto.jp,kikugawa.shizuoka.jp,kim,kimino.wakayama.jp,kimitsu.chiba.jp,kimobetsu.hokkaido.jp,kin.okinawa.jp,kinder,kindle,kinghost.net,kinko.kagoshima.jp,kinokawa.wakayama.jp,kira.aichi.jp,kirkenes.no,kirovograd.ua,kiryu.gunma.jp,kisarazu.chiba.jp,kishiwada.osaka.jp,kiso.nagano.jp,kisofukushima.nagano.jp,kisosaki.mie.jp,kita.kyoto.jp,kita.osaka.jp,kita.tokyo.jp,kitaaiki.nagano.jp,kitaakita.akita.jp,kitadaito.okinawa.jp,kitagata.gifu.jp,kitagata.saga.jp,kitagawa.kochi.jp,kitagawa.miyazaki.jp,kitahata.saga.jp,kitahiroshima.hokkaido.jp,kitakami.iwate.jp,kitakata.fukushima.jp,kitakata.miyazaki.jp,kitakyushu.jp,kitami.hokkaido.jp,kitamoto.saitama.jp,kitanakagusuku.okinawa.jp,kitashiobara.fukushima.jp,kitaura.miyazaki.jp,kitayama.wakayama.jp,kitchen,kiwa.mie.jp,kiwi,kiwi.nz,kiyama.saga.jp,kiyokawa.kanagawa.jp,kiyosato.hokkaido.jp,kiyose.tokyo.jp,kiyosu.aichi.jp,kizu.kyoto.jp,klabu.no,klepp.no,klodzko.pl,klæbu.no,km,km.ua,kmpsp.gov.pl,kn,knightpoint.systems,knowsitall.info,knx-server.net,kobayashi.miyazaki.jp,kobe.jp,kobierzyce.pl,kochi.jp,kochi.kochi.jp,kodaira.tokyo.jp,koebenhavn.museum,koeln,koeln.museum,kofu.yamanashi.jp,koga.fukuoka.jp,koga.ibaraki.jp,koganei.tokyo.jp,koge.tottori.jp,koka.shiga.jp,kokonoe.oita.jp,kokubunji.tokyo.jp,kolobrzeg.pl,komae.tokyo.jp,komagane.nagano.jp,komaki.aichi.jp,komatsu,komatsu.ishikawa.jp,komatsushima.tokushima.jp,komforb.se,kommunalforbund.se,kommune.no,komono.mie.jp,komoro.nagano.jp,komvux.se,konan.aichi.jp,konan.shiga.jp,kongsberg.no,kongsvinger.no,konin.pl,konskowola.pl,konsulat.gov.pl,konyvelo.hu,koori.fukushima.jp,kopervik.no,koriyama.fukushima.jp,koryo.nara.jp,kosai.shizuoka.jp,kosaka.akita.jp,kosei.shiga.jp,kosher,koshigaya.saitama.jp,koshimizu.hokkaido.jp,koshu.yamanashi.jp,kosuge.yamanashi.jp,kota.aichi.jp,koto.shiga.jp,koto.tokyo.jp,kotohira.kagawa.jp,kotoura.tottori.jp,kouhoku.saga.jp,kounosu.saitama.jp,kouyama.kagoshima.jp,kouzushima.tokyo.jp,koya.wakayama.jp,koza.wakayama.jp,kozagawa.wakayama.jp,kozaki.chiba.jp,kozow.com,kp,kpmg,kpn,kppsp.gov.pl,kr,kr.com,kr.eu.org,kr.it,kr.ua,kraanghke.no,kragero.no,kragerø.no,krakow.pl,krasnik.pl,krasnodar.su,krd,kred,kristiansand.no,kristiansund.no,krodsherad.no,krokstadelva.no,krym.ua,kråanghke.no,krødsherad.no,ks.ua,ks.us,kuchinotsu.nagasaki.jp,kudamatsu.yamaguchi.jp,kudoyama.wakayama.jp,kui.hiroshima.jp,kuji.iwate.jp,kuju.oita.jp,kujukuri.chiba.jp,kuki.saitama.jp,kumagaya.saitama.jp,kumakogen.ehime.jp,kumamoto.jp,kumamoto.kumamoto.jp,kumano.hiroshima.jp,kumano.mie.jp,kumatori.osaka.jp,kumejima.okinawa.jp,kumenan.okayama.jp,kumiyama.kyoto.jp,kunden.ortsinfo.at,kunigami.okinawa.jp,kunimi.fukushima.jp,kunisaki.oita.jp,kunitachi.tokyo.jp,kunitomi.miyazaki.jp,kunneppu.hokkaido.jp,kunohe.iwate.jp,kunst.museum,kunstsammlung.museum,kunstunddesign.museum,kuokgroup,kurashiki.okayama.jp,kurate.fukuoka.jp,kure.hiroshima.jp,kurgan.su,kuriyama.hokkaido.jp,kurobe.toyama.jp,kurogi.fukuoka.jp,kuroishi.aomori.jp,kuroiso.tochigi.jp,kuromatsunai.hokkaido.jp,kurotaki.nara.jp,kurume.fukuoka.jp,kusatsu.gunma.jp,kusatsu.shiga.jp,kushima.miyazaki.jp,kushimoto.wakayama.jp,kushiro.hokkaido.jp,kustanai.ru,kustanai.su,kusu.oita.jp,kutchan.hokkaido.jp,kutno.pl,kuwana.mie.jp,kuzumaki.iwate.jp,kv.ua,kvafjord.no,kvalsund.no,kvam.no,kvanangen.no,kvinesdal.no,kvinnherad.no,kviteseid.no,kvitsoy.no,kvitsøy.no,kvæfjord.no,kvænangen.no,kw,kwp.gov.pl,kwpsp.gov.pl,ky,ky.us,kyiv.ua,kyonan.chiba.jp,kyotamba.kyoto.jp,kyotanabe.kyoto.jp,kyotango.kyoto.jp,kyoto,kyoto.jp,kyowa.akita.jp,kyowa.hokkaido.jp,kyuragi.saga.jp,kz,kárášjohka.no,kåfjord.no,l-o-g-i-n.de,l.bg,l.se,la,la-spezia.it,la.us,laakesvuemie.no,lab.ms,labor.museum,labour.museum,lacaixa,ladbrokes,lahppi.no,lajolla.museum,lakas.hu,lamborghini,lamer,lanbib.se,lancashire.museum,lancaster,lancia,lancome,land,land-4-sale.us,landes.museum,landing.myjino.ru,landrover,langevag.no,langevåg.no,lans.museum,lanxess,lapy.pl,laquila.it,lardal.no,larsson.museum,larvik.no,lasalle,laspezia.it,lat,latina.it,latino,latrobe,lavagis.no,lavangen.no,law,law.pro,law.za,lawyer,laz.it,lazio.it,lb,lc,lc.it,lcl.dev,lcube-server.de,lds,le.it,leadpages.co,leangaviika.no,lease,leasing.aero,leaŋgaviika.no,lebesby.no,lebork.pl,lebtimnetz.de,lecce.it,lecco.it,leclerc,leczna.pl,lefrak,leg.br,legal,legnica.pl,lego,leikanger.no,leirfjord.no,leirvik.no,leitungsen.de,leka.no,leksvik.no,lel.br,lelux.site,lenug.su,lenvik.no,lerdal.no,lesja.no,levanger.no,lewismiller.museum,lexus,lezajsk.pl,lg.jp,lg.ua,lgbt,li,li.it,liaison,lib.ak.us,lib.al.us,lib.ar.us,lib.as.us,lib.az.us,lib.ca.us,lib.co.us,lib.ct.us,lib.dc.us,lib.de.us,lib.ee,lib.fl.us,lib.ga.us,lib.gu.us,lib.hi.us,lib.ia.us,lib.id.us,lib.il.us,lib.in.us,lib.ks.us,lib.ky.us,lib.la.us,lib.ma.us,lib.md.us,lib.me.us,lib.mi.us,lib.mn.us,lib.mo.us,lib.ms.us,lib.mt.us,lib.nc.us,lib.nd.us,lib.ne.us,lib.nh.us,lib.nj.us,lib.nm.us,lib.nv.us,lib.ny.us,lib.oh.us,lib.ok.us,lib.or.us,lib.pa.us,lib.pr.us,lib.ri.us,lib.sc.us,lib.sd.us,lib.tn.us,lib.tx.us,lib.ut.us,lib.va.us,lib.vi.us,lib.vt.us,lib.wa.us,lib.wi.us,lib.wy.us,lidl,lier.no,lierne.no,life,lifeinsurance,lifestyle,lig.it,lighting,liguria.it,like,likes-pie.com,likescandy.com,lillehammer.no,lillesand.no,lilly,lima-city.at,lima-city.ch,lima-city.de,lima-city.rocks,lima.zone,limanowa.pl,limited,limo,lincoln,lincoln.museum,lindas.no,linde,lindesnes.no,lindås.no,link,linkitools.space,linkyard-cloud.ch,linkyard.cloud,linz.museum,lipsy,live,living,living.museum,livinghistory.museum,livorno.it,lixil,lk,llc,ln.cn,lo.it,loabat.no,loabát.no,loan,loans,localhistory.museum,localhost.daplie.me,locker,locus,lodi.it,lodingen.no,loft,loginline.app,loginline.dev,loginline.io,loginline.services,loginline.site,loginto.me,logistics.aero,logoip.com,logoip.de,lol,lom.it,lom.no,lombardia.it,lombardy.it,lomza.pl,london,london.cloudapps.digital,london.museum,londrina.br,loppa.no,lorenskog.no,losangeles.museum,loseyourip.com,loten.no,lotte,lotto,louvre.museum,love,lowicz.pl,loyalist.museum,lpages.co,lpl,lplfinancial,lpusercontent.com,lr,ls,lt,lt.eu.org,lt.it,lt.ua,ltd,ltd.co.im,ltd.cy,ltd.gi,ltd.hk,ltd.lk,ltd.ng,ltd.ua,ltd.uk,ltda,lu,lu.eu.org,lu.it,lubartow.pl,lubin.pl,lublin.pl,lucania.it,lucca.it,lucerne.museum,lug.org.uk,lugansk.ua,lugs.org.uk,lukow.pl,lund.no,lundbeck,lunner.no,lupin,luroy.no,lurøy.no,luster.no,lutsk.ua,luxe,luxembourg.museum,luxury,luzern.museum,lv,lv.eu.org,lv.ua,lviv.ua,ly,lyngdal.no,lyngen.no,láhppi.no,läns.museum,lærdal.no,lødingen.no,lørenskog.no,løten.no,m.bg,m.se,ma,ma.gov.br,ma.leg.br,ma.us,macapa.br,maceio.br,macerata.it,machida.tokyo.jp,macys,mad.museum,madrid,madrid.museum,maebashi.gunma.jp,magazine.aero,magentosite.cloud,maibara.shiga.jp,maif,mail.pl,maintenance.aero,maison,maizuru.kyoto.jp,makeup,makinohara.shizuoka.jp,makurazaki.kagoshima.jp,malatvuopmi.no,malbork.pl,mallorca.museum,malopolska.pl,malselv.no,malvik.no,mamurogawa.yamagata.jp,man,management,manaus.br,manchester.museum,mandal.no,mango,mangyshlak.su,maniwa.okayama.jp,manno.kagawa.jp,mansion.museum,mansions.museum,mantova.it,manx.museum,maori.nz,map,map.fastly.net,map.fastlylb.net,mar.it,marburg.museum,marche.it,marine.ru,maringa.br,maritime.museum,maritimo.museum,marker.no,market,marketing,markets,marnardal.no,marriott,marshalls,marugame.kagawa.jp,marumori.miyagi.jp,maryland.museum,marylhurst.museum,masaki.ehime.jp,maserati,masfjorden.no,mashike.hokkaido.jp,mashiki.kumamoto.jp,mashiko.tochigi.jp,masoy.no,massa-carrara.it,massacarrara.it,masuda.shimane.jp,mat.br,matera.it,matsubara.osaka.jp,matsubushi.saitama.jp,matsuda.kanagawa.jp,matsudo.chiba.jp,matsue.shimane.jp,matsukawa.nagano.jp,matsumae.hokkaido.jp,matsumoto.kagoshima.jp,matsumoto.nagano.jp,matsuno.ehime.jp,matsusaka.mie.jp,matsushige.tokushima.jp,matsushima.miyagi.jp,matsuura.nagasaki.jp,matsuyama.ehime.jp,matsuzaki.shizuoka.jp,matta-varjjat.no,mattel,mayfirst.info,mayfirst.org,mazowsze.pl,mazury.pl,mb.ca,mb.it,mba,mc,mc.eu.org,mc.it,mckinsey,md,md.ci,md.us,me,me.eu.org,me.it,me.ke,me.tz,me.uk,me.us,med,med.br,med.ec,med.ee,med.ht,med.ly,med.om,med.pa,med.pl,med.pro,med.sa,med.sd,medecin.fr,medecin.km,media,media.aero,media.hu,media.museum,media.pl,medical.museum,medicina.bo,medio-campidano.it,mediocampidano.it,medizinhistorisches.museum,meeres.museum,meet,meguro.tokyo.jp,mein-iserv.de,mein-vigor.de,meiwa.gunma.jp,meiwa.mie.jp,meland.no,melbourne,meldal.no,melhus.no,meloy.no,meløy.no,members.linode.com,meme,memorial,memorial.museum,memset.net,men,menu,meraker.no,merckmsd,merseine.nu,meråker.no,mesaverde.museum,messina.it,meteorapp.com,metlife,mex.com,mg,mg.gov.br,mg.leg.br,mh,mi.it,mi.th,mi.us,miami,miasa.nagano.jp,miasta.pl,mibu.tochigi.jp,michigan.museum,microlight.aero,microsoft,midatlantic.museum,midori.chiba.jp,midori.gunma.jp,midsund.no,midtre-gauldal.no,mie.jp,mielec.pl,mielno.pl,mifune.kumamoto.jp,mihama.aichi.jp,mihama.chiba.jp,mihama.fukui.jp,mihama.mie.jp,mihama.wakayama.jp,mihara.hiroshima.jp,mihara.kochi.jp,miharu.fukushima.jp,miho.ibaraki.jp,mikasa.hokkaido.jp,mikawa.yamagata.jp,miki.hyogo.jp,mil,mil.ac,mil.ae,mil.al,mil.ar,mil.az,mil.ba,mil.bo,mil.br,mil.by,mil.cl,mil.cn,mil.co,mil.do,mil.ec,mil.eg,mil.ge,mil.gh,mil.gt,mil.hn,mil.id,mil.in,mil.iq,mil.jo,mil.kg,mil.km,mil.kr,mil.kz,mil.lv,mil.mg,mil.mv,mil.my,mil.mz,mil.ng,mil.ni,mil.no,mil.nz,mil.pe,mil.ph,mil.pl,mil.py,mil.qa,mil.ru,mil.rw,mil.sh,mil.st,mil.sy,mil.tj,mil.tm,mil.to,mil.tr,mil.tw,mil.tz,mil.uy,mil.vc,mil.ve,mil.za,mil.zm,mil.zw,milan.it,milano.it,military.museum,mill.museum,mima.tokushima.jp,mimata.miyazaki.jp,minakami.gunma.jp,minamata.kumamoto.jp,minami-alps.yamanashi.jp,minami.fukuoka.jp,minami.kyoto.jp,minami.tokushima.jp,minamiaiki.nagano.jp,minamiashigara.kanagawa.jp,minamiawaji.hyogo.jp,minamiboso.chiba.jp,minamidaito.okinawa.jp,minamiechizen.fukui.jp,minamifurano.hokkaido.jp,minamiise.mie.jp,minamiizu.shizuoka.jp,minamimaki.nagano.jp,minamiminowa.nagano.jp,minamioguni.kumamoto.jp,minamisanriku.miyagi.jp,minamitane.kagoshima.jp,minamiuonuma.niigata.jp,minamiyamashiro.kyoto.jp,minano.saitama.jp,minato.osaka.jp,minato.tokyo.jp,mincom.tn,mine.nu,miners.museum,mini,mining.museum,miniserver.com,minnesota.museum,mino.gifu.jp,minobu.yamanashi.jp,minoh.osaka.jp,minokamo.gifu.jp,minowa.nagano.jp,mint,misaki.okayama.jp,misaki.osaka.jp,misasa.tottori.jp,misato.akita.jp,misato.miyagi.jp,misato.saitama.jp,misato.shimane.jp,misato.wakayama.jp,misawa.aomori.jp,misconfused.org,mishima.fukushima.jp,mishima.shizuoka.jp,missile.museum,missoula.museum,misugi.mie.jp,mit,mitaka.tokyo.jp,mitake.gifu.jp,mitane.akita.jp,mito.ibaraki.jp,mitou.yamaguchi.jp,mitoyo.kagawa.jp,mitsubishi,mitsue.nara.jp,mitsuke.niigata.jp,miura.kanagawa.jp,miyada.nagano.jp,miyagi.jp,miyake.nara.jp,miyako.fukuoka.jp,miyako.iwate.jp,miyakonojo.miyazaki.jp,miyama.fukuoka.jp,miyama.mie.jp,miyashiro.saitama.jp,miyawaka.fukuoka.jp,miyazaki.jp,miyazaki.miyazaki.jp,miyazu.kyoto.jp,miyoshi.aichi.jp,miyoshi.hiroshima.jp,miyoshi.saitama.jp,miyoshi.tokushima.jp,miyota.nagano.jp,mizuho.tokyo.jp,mizumaki.fukuoka.jp,mizunami.gifu.jp,mizusawa.iwate.jp,mjondalen.no,mjøndalen.no,mk,mk.eu.org,mk.ua,ml,mlb,mlbfan.org,mls,mm,mma,mmafan.biz,mn,mn.it,mn.us,mo,mo-i-rana.no,mo-siemens.io,mo.cn,mo.it,mo.us,moareke.no,mobara.chiba.jp,mobi,mobi.gp,mobi.ke,mobi.na,mobi.ng,mobi.tt,mobi.tz,mobile,mobily,mochizuki.nagano.jp,mod.gi,moda,modalen.no,modelling.aero,modena.it,modern.museum,modum.no,moe,moi,moka.tochigi.jp,mol.it,molde.no,molise.it,mom,moma.museum,mombetsu.hokkaido.jp,monash,money,money.museum,monmouth.museum,monster,monticello.museum,montreal.museum,monza-brianza.it,monza-e-della-brianza.it,monza.it,monzabrianza.it,monzaebrianza.it,monzaedellabrianza.it,moonscale.io,moonscale.net,mopar,mordovia.ru,mordovia.su,morena.br,moriguchi.osaka.jp,morimachi.shizuoka.jp,morioka.iwate.jp,moriya.ibaraki.jp,moriyama.shiga.jp,moriyoshi.akita.jp,mormon,morotsuka.miyazaki.jp,moroyama.saitama.jp,mortgage,moscow,moscow.museum,moseushi.hokkaido.jp,mosjoen.no,mosjøen.no,moskenes.no,moss.no,mosvik.no,motegi.tochigi.jp,moto,motobu.okinawa.jp,motorcycle.museum,motorcycles,motosu.gifu.jp,motoyama.kochi.jp,mov,movie,movimiento.bo,movistar,mozilla-iot.org,moåreke.no,mp,mp.br,mq,mr,mr.no,mragowo.pl,ms,ms.gov.br,ms.it,ms.kr,ms.leg.br,ms.us,msd,msk.ru,msk.su,mt,mt.eu.org,mt.gov.br,mt.it,mt.leg.br,mt.us,mtn,mtr,mu,muenchen.museum,muenster.museum,mugi.tokushima.jp,muika.niigata.jp,mukawa.hokkaido.jp,muko.kyoto.jp,mulhouse.museum,munakata.fukuoka.jp,muncie.museum,muni.il,muosat.no,muosát.no,mup.gov.pl,murakami.niigata.jp,murata.miyagi.jp,murayama.yamagata.jp,murmansk.su,muroran.hokkaido.jp,muroto.kochi.jp,mus.br,mus.mi.us,musashimurayama.tokyo.jp,musashino.tokyo.jp,museet.museum,museum,museum.mv,museum.mw,museum.no,museum.om,museum.tt,museumcenter.museum,museumvereniging.museum,music.museum,musica.ar,musica.bo,mutsu.aomori.jp,mutsuzawa.chiba.jp,mutual,mv,mw,mw.gov.pl,mx,mx.na,my,my-firewall.org,my-gateway.de,my-router.de,my-vigor.de,my-wan.de,my.eu.org,my.id,myactivedirectory.com,myasustor.com,mycd.eu,mydatto.com,mydatto.net,myddns.rocks,mydissent.net,mydobiss.com,mydrobo.com,myds.me,myeffect.net,myfirewall.org,myfritz.net,myftp.biz,myftp.org,myhome-server.de,myiphost.com,myjino.ru,mykolaiv.ua,mymailer.com.tw,mymediapc.net,myoko.niigata.jp,mypep.link,mypets.ws,myphotos.cc,mypi.co,mypsx.net,myqnapcloud.com,myravendb.com,mysecuritycamera.com,mysecuritycamera.net,mysecuritycamera.org,myshopblocks.com,mytis.ru,mytuleap.com,myvnc.com,mywire.org,mz,málatvuopmi.no,mátta-várjjat.no,målselv.no,måsøy.no,māori.nz,n.bg,n.se,n4t.co,na,na.it,naamesjevuemie.no,nab,nabari.mie.jp,nachikatsuura.wakayama.jp,nadex,nagahama.shiga.jp,nagai.yamagata.jp,nagano.jp,nagano.nagano.jp,naganohara.gunma.jp,nagaoka.niigata.jp,nagaokakyo.kyoto.jp,nagara.chiba.jp,nagareyama.chiba.jp,nagasaki.jp,nagasaki.nagasaki.jp,nagasu.kumamoto.jp,nagato.yamaguchi.jp,nagatoro.saitama.jp,nagawa.nagano.jp,nagi.okayama.jp,nagiso.nagano.jp,nago.okinawa.jp,nagoya,nagoya.jp,naha.okinawa.jp,nahari.kochi.jp,naie.hokkaido.jp,naka.hiroshima.jp,naka.ibaraki.jp,nakadomari.aomori.jp,nakagawa.fukuoka.jp,nakagawa.hokkaido.jp,nakagawa.nagano.jp,nakagawa.tokushima.jp,nakagusuku.okinawa.jp,nakagyo.kyoto.jp,nakai.kanagawa.jp,nakama.fukuoka.jp,nakamichi.yamanashi.jp,nakamura.kochi.jp,nakaniikawa.toyama.jp,nakano.nagano.jp,nakano.tokyo.jp,nakanojo.gunma.jp,nakanoto.ishikawa.jp,nakasatsunai.hokkaido.jp,nakatane.kagoshima.jp,nakatombetsu.hokkaido.jp,nakatsugawa.gifu.jp,nakayama.yamagata.jp,nakijin.okinawa.jp,naklo.pl,nalchik.ru,nalchik.su,namdalseid.no,name,name.az,name.cy,name.eg,name.et,name.hr,name.jo,name.mk,name.mv,name.my,name.na,name.ng,name.pr,name.qa,name.tj,name.tr,name.tt,name.vn,namegata.ibaraki.jp,namegawa.saitama.jp,namerikawa.toyama.jp,namie.fukushima.jp,namikata.ehime.jp,namsos.no,namsskogan.no,nanae.hokkaido.jp,nanao.ishikawa.jp,nanbu.tottori.jp,nanbu.yamanashi.jp,nango.fukushima.jp,nanjo.okinawa.jp,nankoku.kochi.jp,nanmoku.gunma.jp,nannestad.no,nanporo.hokkaido.jp,nantan.kyoto.jp,nanto.toyama.jp,nanyo.yamagata.jp,naoshima.kagawa.jp,naples.it,napoli.it,nara.jp,nara.nara.jp,narashino.chiba.jp,narita.chiba.jp,naroy.no,narusawa.yamanashi.jp,naruto.tokushima.jp,narviika.no,narvik.no,nasu.tochigi.jp,nasushiobara.tochigi.jp,nat.tn,natal.br,national.museum,nationalfirearms.museum,nationalheritage.museum,nationwide,nativeamerican.museum,natori.miyagi.jp,natura,natural.bo,naturalhistory.museum,naturalhistorymuseum.museum,naturalsciences.museum,naturbruksgymn.se,nature.museum,naturhistorisches.museum,natuurwetenschappen.museum,naumburg.museum,naustdal.no,naval.museum,navigation.aero,navoi.su,navuotna.no,navy,nayoro.hokkaido.jp,nb.ca,nba,nc,nc.tr,nc.us,nctu.me,nd.us,ne,ne.jp,ne.ke,ne.kr,ne.pw,ne.tz,ne.ug,ne.us,neat-url.com,nebraska.museum,nec,nedre-eiker.no,nemuro.hokkaido.jp,nerdpol.ovh,nerima.tokyo.jp,nes.akershus.no,nes.buskerud.no,nesna.no,nesodden.no,nesoddtangen.no,nesseby.no,nesset.no,net,net-freaks.com,net.ac,net.ae,net.af,net.ag,net.ai,net.al,net.am,net.ar,net.au,net.az,net.ba,net.bb,net.bh,net.bm,net.bn,net.bo,net.br,net.bs,net.bt,net.bz,net.ci,net.cm,net.cn,net.co,net.cu,net.cw,net.cy,net.dm,net.do,net.dz,net.ec,net.eg,net.et,net.eu.org,net.ge,net.gg,net.gl,net.gn,net.gp,net.gr,net.gt,net.gu,net.gy,net.hk,net.hn,net.ht,net.id,net.il,net.im,net.in,net.iq,net.ir,net.is,net.je,net.jo,net.kg,net.ki,net.kn,net.kw,net.ky,net.kz,net.la,net.lb,net.lc,net.lk,net.lr,net.ls,net.lv,net.ly,net.ma,net.me,net.mk,net.ml,net.mo,net.ms,net.mt,net.mu,net.mv,net.mw,net.mx,net.my,net.mz,net.nf,net.ng,net.ni,net.nr,net.nz,net.om,net.pa,net.pe,net.ph,net.pk,net.pl,net.pn,net.pr,net.ps,net.pt,net.py,net.qa,net.ru,net.rw,net.sa,net.sb,net.sc,net.sd,net.sg,net.sh,net.sl,net.so,net.st,net.sy,net.th,net.tj,net.tm,net.tn,net.to,net.tr,net.tt,net.tw,net.ua,net.uk,net.uy,net.uz,net.vc,net.ve,net.vi,net.vn,net.vu,net.ws,net.za,net.zm,netbank,netflix,netlify.com,network,neues.museum,neustar,new,newhampshire.museum,newholland,newjersey.museum,newmexico.museum,newport.museum,news,news.hu,newspaper.museum,newyork.museum,next,nextdirect,nexus,neyagawa.osaka.jp,nf,nf.ca,nfl,nflfan.org,nfshost.com,ng,ng.city,ng.eu.org,ng.ink,ng.school,ngo,ngo.lk,ngo.ph,ngo.za,ngrok.io,nh-serv.co.uk,nh.us,nhk,nhlfan.net,nhs.uk,ni,nic.in,nic.tj,nic.za,nichinan.miyazaki.jp,nichinan.tottori.jp,nico,nid.io,niepce.museum,nieruchomosci.pl,niigata.jp,niigata.niigata.jp,niihama.ehime.jp,niikappu.hokkaido.jp,niimi.okayama.jp,niiza.saitama.jp,nikaho.akita.jp,nike,niki.hokkaido.jp,nikko.tochigi.jp,nikolaev.ua,nikon,ninja,ninohe.iwate.jp,ninomiya.kanagawa.jp,nirasaki.yamanashi.jp,nis.za,nishi.fukuoka.jp,nishi.osaka.jp,nishiaizu.fukushima.jp,nishiarita.saga.jp,nishiawakura.okayama.jp,nishiazai.shiga.jp,nishigo.fukushima.jp,nishihara.kumamoto.jp,nishihara.okinawa.jp,nishiizu.shizuoka.jp,nishikata.tochigi.jp,nishikatsura.yamanashi.jp,nishikawa.yamagata.jp,nishimera.miyazaki.jp,nishinomiya.hyogo.jp,nishinoomote.kagoshima.jp,nishinoshima.shimane.jp,nishio.aichi.jp,nishiokoppe.hokkaido.jp,nishitosa.kochi.jp,nishiwaki.hyogo.jp,nissan,nissay,nissedal.no,nisshin.aichi.jp,niteroi.br,nittedal.no,niyodogawa.kochi.jp,nj.us,nl,nl.ca,nl.eu.org,nl.no,nm.cn,nm.us,no,no-ip.biz,no-ip.ca,no-ip.co.uk,no-ip.info,no-ip.net,no-ip.org,no.com,no.eu.org,no.it,nobeoka.miyazaki.jp,noboribetsu.hokkaido.jp,noda.chiba.jp,noda.iwate.jp,nodebalancer.linode.com,nodum.co,nodum.io,nogata.fukuoka.jp,nogi.tochigi.jp,noheji.aomori.jp,noho.st,nohost.me,noip.me,noip.us,nokia,nom.ad,nom.ae,nom.af,nom.ag,nom.ai,nom.al,nom.br,nom.cl,nom.co,nom.es,nom.fr,nom.gd,nom.ge,nom.gl,nom.gt,nom.hn,nom.im,nom.ke,nom.km,nom.li,nom.mg,nom.mk,nom.nc,nom.ni,nom.nu,nom.pa,nom.pe,nom.pl,nom.pw,nom.qa,nom.re,nom.ro,nom.rs,nom.si,nom.st,nom.tj,nom.tm,nom.ug,nom.uy,nom.vc,nom.vg,nom.za,nombre.bo,nome.pt,nomi.ishikawa.jp,nonoichi.ishikawa.jp,nord-aurdal.no,nord-fron.no,nord-odal.no,norddal.no,nordkapp.no,nordre-land.no,nordreisa.no,nore-og-uvdal.no,norfolk.museum,north-kazakhstan.su,north.museum,northwesternmutual,norton,nose.osaka.jp,nosegawa.nara.jp,noshiro.akita.jp,not.br,notaires.fr,notaires.km,noticias.bo,noto.ishikawa.jp,notodden.no,notogawa.shiga.jp,notteroy.no,nov.ru,nov.su,novara.it,now,now-dns.net,now-dns.org,now-dns.top,now.sh,nowaruda.pl,nowruz,nowtv,nozawaonsen.nagano.jp,np,nr,nra,nrw,nrw.museum,ns.ca,nsn.us,nsupdate.info,nsw.au,nsw.edu.au,nt.au,nt.ca,nt.edu.au,nt.no,nt.ro,ntdll.top,ntr.br,ntt,nu,nu.ca,nu.it,nuernberg.museum,numata.gunma.jp,numata.hokkaido.jp,numazu.shizuoka.jp,nuoro.it,nuremberg.museum,nv.us,nx.cn,ny.us,nyc,nyc.mn,nyc.museum,nym.by,nym.bz,nym.ec,nym.gr,nym.gy,nym.hk,nym.ie,nym.kz,nym.la,nym.lc,nym.li,nym.lt,nym.lu,nym.me,nym.mn,nym.mx,nym.nz,nym.pe,nym.pt,nym.ro,nym.sk,nym.su,nym.sx,nym.tw,nyny.museum,nysa.pl,nyuzen.toyama.jp,nz,nz.eu.org,návuotna.no,nååmesjevuemie.no,nærøy.no,nøtterøy.no,o.bg,o.se,oamishirasato.chiba.jp,oarai.ibaraki.jp,obama.fukui.jp,obama.nagasaki.jp,obanazawa.yamagata.jp,obi,obihiro.hokkaido.jp,obira.hokkaido.jp,obninsk.su,observer,obu.aichi.jp,obuse.nagano.jp,oceanographic.museum,oceanographique.museum,ochi.kochi.jp,od.ua,odate.akita.jp,odawara.kanagawa.jp,odda.no,odesa.ua,odessa.ua,odo.br,oe.yamagata.jp,of.by,of.fashion,of.football,of.london,of.no,of.work,off,off.ai,office,office-on-the.net,official.academy,ofunato.iwate.jp,og.ao,og.it,oga.akita.jp,ogaki.gifu.jp,ogano.saitama.jp,ogasawara.tokyo.jp,ogata.akita.jp,ogawa.ibaraki.jp,ogawa.nagano.jp,ogawa.saitama.jp,ogawara.miyagi.jp,ogi.saga.jp,ogimi.okinawa.jp,ogliastra.it,ogori.fukuoka.jp,ogose.saitama.jp,oguchi.aichi.jp,oguni.kumamoto.jp,oguni.yamagata.jp,oh.us,oharu.aichi.jp,ohda.shimane.jp,ohi.fukui.jp,ohira.miyagi.jp,ohira.tochigi.jp,ohkura.yamagata.jp,ohtawara.tochigi.jp,o
gitextract_qe_rn8jq/
├── .gitignore
├── .vscode/
│ ├── launch.json
│ └── tasks.json
├── LICENSE
├── README.md
├── src/
│ ├── home/
│ │ ├── 404.html
│ │ ├── README.md
│ │ ├── assets/
│ │ │ ├── cors_v1.txt
│ │ │ └── index_v3.html
│ │ ├── build.sh
│ │ ├── conf.js
│ │ └── sw.js
│ └── proxy/
│ ├── .eslintrc.json
│ ├── .package-lock.json
│ ├── .package.json
│ ├── README.md
│ ├── debug.sh
│ ├── gen-tld/
│ │ ├── README.md
│ │ └── gen.js
│ ├── jsconfig.json
│ ├── release.sh
│ └── src/
│ ├── cdn.js
│ ├── client.js
│ ├── cookie.js
│ ├── database.js
│ ├── env.js
│ ├── fakeloc.js
│ ├── hook.js
│ ├── index.js
│ ├── inject.js
│ ├── jsfilter.js
│ ├── msg.js
│ ├── network.js
│ ├── page.js
│ ├── path.js
│ ├── route.js
│ ├── signal.js
│ ├── storage.js
│ ├── sw.js
│ ├── tld-data.js
│ ├── tld.js
│ ├── urlx.js
│ └── util.js
└── www/
├── 404.html
├── assets/
│ ├── README.md
│ ├── cors_v1.txt
│ └── index_v3.html
├── conf.js
└── sw.js
SYMBOL INDEX (178 symbols across 22 files)
FILE: src/proxy/gen-tld/gen.js
constant SRC_PATH (line 5) | const SRC_PATH = 'https://publicsuffix.org/list/effective_tld_names.dat'
constant DST_PATH (line 6) | const DST_PATH = '../src/tld-data.js'
function proc (line 22) | function proc(str) {
FILE: src/proxy/src/cdn.js
constant CDN (line 4) | const CDN = 'https://cdn.jsdelivr.net/npm/jsproxy-cache-01@0.0.'
function loadDirectList (line 15) | async function loadDirectList(conf) {
function loadStaticList (line 27) | async function loadStaticList(conf) {
function setConf (line 55) | function setConf(conf) {
function isDirectHost (line 65) | function isDirectHost(host) {
function proxyDirect (line 73) | async function proxyDirect(url) {
function getFileVer (line 92) | function getFileVer(urlHash) {
function proxyStaticMain (line 101) | async function proxyStaticMain(urlHash, urlVer) {
function proxyStatic (line 128) | async function proxyStatic(urlHash, urlVer) {
FILE: src/proxy/src/client.js
function init (line 21) | function init(global, origin) {
FILE: src/proxy/src/cookie.js
function Cookie (line 7) | function Cookie() {
function copy (line 25) | function copy(dst, src) {
function isSubPath (line 44) | function isSubPath(cookiePath, urlPath) {
function isSubDomain (line 59) | function isSubDomain(cookieDomain, urlDomain) {
function isExpire (line 69) | function isExpire(item, now) {
class CookieDomainNode (line 75) | class CookieDomainNode {
method constructor (line 76) | constructor() {
method nextChild (line 87) | nextChild(name) {
method getChild (line 96) | getChild(name) {
method addCookie (line 103) | addCookie(cookie) {
function getNonHttpOnlyItems (line 119) | function getNonHttpOnlyItems() {
function parse (line 135) | function parse(str, urlObj, now) {
function set (line 262) | function set(item) {
function query (line 298) | function query(urlObj) {
function setDB (line 359) | async function setDB(db) {
function save (line 376) | async function save() {
FILE: src/proxy/src/database.js
class Database (line 4) | class Database {
method constructor (line 8) | constructor(name) {
method _getStore (line 19) | _getStore(table, mode) {
method open (line 28) | open(opts) {
method close (line 56) | close() {
method get (line 64) | get(table, key) {
method put (line 82) | put(table, record) {
method delete (line 100) | delete(table, key) {
method enum (line 118) | enum(table, callback, ...args) {
FILE: src/proxy/src/env.js
constant ENV_PAGE (line 1) | const ENV_PAGE = 1
constant ENV_WORKER (line 2) | const ENV_WORKER = 2
constant ENV_SW (line 3) | const ENV_SW = 3
function setEnvType (line 7) | function setEnvType(v) {
function isSwEnv (line 11) | function isSwEnv() {
function isWorkerEnv (line 15) | function isWorkerEnv() {
function add (line 25) | function add(win, info) {
function get (line 29) | function get(obj) {
FILE: src/proxy/src/fakeloc.js
function setup (line 9) | function setup(obj, fakeLoc) {
function createFakeLoc (line 25) | function createFakeLoc(global) {
FILE: src/proxy/src/hook.js
constant DROP (line 8) | const DROP = {}
function func (line 17) | function func(obj, key, factory) {
function prop (line 47) | function prop(obj, key, g, s) {
function createDomHook (line 66) | function createDomHook(win) {
FILE: src/proxy/src/index.js
function pageEnv (line 4) | function pageEnv(win) {
function swEnv (line 41) | function swEnv() {
function workerEnv (line 47) | function workerEnv(global) {
function main (line 57) | function main(global) {
FILE: src/proxy/src/inject.js
constant WORKER_INJECT (line 8) | const WORKER_INJECT = util.strToBytes(`\
function getWorkerCode (line 16) | function getWorkerCode() {
function setConf (line 21) | function setConf(conf) {
constant PADDING (line 26) | const PADDING = ' '.repeat(500)
constant CSP (line 28) | const CSP = `\
function getHtmlCode (line 43) | function getHtmlCode(urlObj, pageId) {
FILE: src/proxy/src/jsfilter.js
function parseStr (line 7) | function parseStr(code) {
function parseBin (line 29) | function parseBin(buf, charset) {
FILE: src/proxy/src/msg.js
constant PAGE_INFO_PULL (line 1) | const PAGE_INFO_PULL = 1
constant SW_INFO_PUSH (line 2) | const SW_INFO_PUSH = 2
constant PAGE_COOKIE_PUSH (line 4) | const PAGE_COOKIE_PUSH = 3
constant SW_COOKIE_PUSH (line 5) | const SW_COOKIE_PUSH = 4
constant PAGE_INIT_BEG (line 7) | const PAGE_INIT_BEG = 5
constant PAGE_INIT_END (line 8) | const PAGE_INIT_END = 6
constant PAGE_CONF_SET (line 10) | const PAGE_CONF_SET = 110
constant PAGE_CONF_GET (line 11) | const PAGE_CONF_GET = 111
constant PAGE_RELOAD_CONF (line 12) | const PAGE_RELOAD_CONF = 112
constant SW_CONF_RETURN (line 14) | const SW_CONF_RETURN = 112
constant SW_CONF_CHANGE (line 15) | const SW_CONF_CHANGE = 113
constant PAGE_READY_CHECK (line 17) | const PAGE_READY_CHECK = 200
constant SW_READY (line 18) | const SW_READY = 201
FILE: src/proxy/src/network.js
constant REFER_ORIGIN (line 10) | const REFER_ORIGIN = location.origin + '/'
constant ENABLE_3RD_COOKIE (line 11) | const ENABLE_3RD_COOKIE = true
function setConf (line 28) | function setConf(conf) {
function setDB (line 34) | async function setDB(db) {
function getUrlCache (line 43) | function getUrlCache(url) {
function setUrlCache (line 54) | async function setUrlCache(url, host, info, expires) {
function delUrlCache (line 62) | async function delUrlCache(url) {
function getReqCookie (line 72) | function getReqCookie(targetUrlObj, clientUrlObj, req) {
function parseResCache (line 92) | function parseResCache(header) {
function procResCookie (line 122) | function procResCookie(cookieStrArr, urlObj, cliUrlObj) {
function getResInfo (line 151) | function getResInfo(res) {
constant R_UNSAFE_REQ_HDR_CHAR (line 213) | const R_UNSAFE_REQ_HDR_CHAR =
function isSimpleReqHdr (line 221) | function isSimpleReqHdr(key, val) {
function initReqHdr (line 245) | function initReqHdr(req, urlObj, cliUrlObj) {
function updateReqHeaders (line 292) | function updateReqHeaders(reqOpt, info) {
constant MAX_RETRY (line 297) | const MAX_RETRY = 5
function launch (line 304) | async function launch(req, urlObj, cliUrlObj) {
FILE: src/proxy/src/page.js
function initDoc (line 17) | function initDoc(win, domHook) {
function init (line 85) | function init(win) {
FILE: src/proxy/src/path.js
constant ROOT (line 2) | const ROOT = getRootPath()
constant HOME (line 3) | const HOME = ROOT + 'index.html'
constant CONF (line 4) | const CONF = ROOT + 'conf.js'
constant ICON (line 5) | const ICON = ROOT + 'favicon.ico'
constant HELPER (line 6) | const HELPER = ROOT + '__sys__/helper.js'
constant ASSETS (line 7) | const ASSETS = ROOT + '__sys__/assets/'
constant PREFIX (line 8) | const PREFIX = ROOT + '-----'
function getRootPath (line 11) | function getRootPath() {
FILE: src/proxy/src/route.js
function getHostByNodeId (line 13) | function getHostByNodeId(urlHash, id) {
function isLocalhost (line 26) | function isLocalhost(host) {
function genUrl (line 35) | function genUrl(host, scheme) {
function getHost (line 45) | function getHost(urlHash, level) {
function genWsUrl (line 66) | function genWsUrl(urlObj, args) {
function setConf (line 93) | function setConf(conf) {
FILE: src/proxy/src/signal.js
class Signal (line 43) | class Signal {
method constructor (line 44) | constructor() {
method wait (line 51) | wait() {
method notify (line 55) | notify(arg) {
method abort (line 59) | abort(arg) {
FILE: src/proxy/src/storage.js
function setup (line 20) | function setup(win, name, prefix) {
function createStorage (line 164) | function createStorage(global, origin) {
FILE: src/proxy/src/sw.js
constant CONF_UPDATE_TIMER (line 14) | const CONF_UPDATE_TIMER = 1000 * 60 * 5
constant MAX_REDIR (line 17) | const MAX_REDIR = 5
function sendMsg (line 32) | function sendMsg(target, cmd, val) {
function genPageId (line 47) | function genPageId() {
function pageWait (line 54) | function pageWait(pageId) {
function pageNotify (line 71) | function pageNotify(id, isDone) {
function makeHtmlRes (line 88) | function makeHtmlRes(body, status = 200) {
function processHtml (line 103) | function processHtml(res, resOpt, urlObj) {
function processJs (line 140) | function processJs(buf, charset) {
function sendMsgToPages (line 152) | async function sendMsgToPages(cmd, msg, srcId) {
function getUrlByClientId (line 174) | async function getUrlByClientId(id) {
function parseGatewayError (line 190) | function parseGatewayError(jsonStr, status, urlObj) {
function forward (line 238) | async function forward(req, urlObj, cliUrlObj, redirNum) {
function proxy (line 348) | async function proxy(e, urlObj) {
function initDB (line 371) | async function initDB() {
function onFetch (line 390) | async function onFetch(e) {
function parseUrlHandler (line 465) | function parseUrlHandler(handler) {
function updateConf (line 478) | function updateConf(conf, force) {
function readConf (line 502) | async function readConf() {
function saveConf (line 511) | async function saveConf(conf) {
function loadConf (line 519) | async function loadConf() {
function initConf (line 530) | async function initConf() {
FILE: src/proxy/src/tld.js
function getDomainTld (line 13) | function getDomainTld(domain) {
function getTld (line 34) | function getTld(domain) {
function isTld (line 54) | function isTld(domain) {
FILE: src/proxy/src/urlx.js
constant PREFIX (line 7) | const PREFIX = path.PREFIX
constant PREFIX_LEN (line 8) | const PREFIX_LEN = PREFIX.length
constant ROOT_LEN (line 9) | const ROOT_LEN = path.ROOT.length
function isHttpProto (line 14) | function isHttpProto(url) {
function isInternalUrl (line 22) | function isInternalUrl(url) {
function newUrl (line 31) | function newUrl(url, baseUrl) {
function encUrlObj (line 45) | function encUrlObj(urlObj) {
constant IS_SW (line 53) | const IS_SW = env.isSwEnv()
constant IS_WORKER (line 54) | const IS_WORKER = env.isWorkerEnv()
constant WORKER_URL (line 55) | const WORKER_URL = IS_WORKER && decUrlStrAbs(location.href)
function encUrlStrRel (line 61) | function encUrlStrRel(url, relObj) {
function encUrlStrAbs (line 84) | function encUrlStrAbs(url) {
function decUrlObj (line 96) | function decUrlObj(urlObj) {
function decUrlStrRel (line 109) | function decUrlStrRel(url, relObj) {
function decUrlStrAbs (line 130) | function decUrlStrAbs(url) {
function delHash (line 143) | function delHash(url) {
function delScheme (line 152) | function delScheme(url) {
function replaceHttpRefresh (line 161) | function replaceHttpRefresh(val, relObj) {
constant DEFAULT_ALIAS (line 197) | const DEFAULT_ALIAS = {
constant DEFAULT_SEARCH (line 205) | const DEFAULT_SEARCH = 'https://www.google.com/search?q=%s'
function getAliasUrl (line 213) | function getAliasUrl(alias) {
function padUrl (line 233) | function padUrl(part) {
function adjustNav (line 267) | function adjustNav(urlStr) {
FILE: src/proxy/src/util.js
constant ENC (line 1) | const ENC = new TextEncoder()
function strToBytes (line 6) | function strToBytes(str) {
function bytesToStr (line 14) | function bytesToStr(bytes, charset = 'utf-8') {
function isUtf8 (line 21) | function isUtf8(label) {
constant R_IP (line 26) | const R_IP = /^(?:\d+\.){0,3}\d+$/
function isIPv4 (line 31) | function isIPv4(str) {
constant JS_MIME_SET (line 36) | const JS_MIME_SET = new Set([
function isJsMime (line 47) | function isJsMime(mime) {
function concatBufs (line 56) | function concatBufs(bufs) {
function strHash (line 75) | function strHash(str) {
function numToHex (line 88) | function numToHex(num, len) {
function sleep (line 96) | async function sleep(ms) {
function getTimeSeconds (line 101) | function getTimeSeconds() {
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (268K chars).
[
{
"path": ".gitignore",
"chars": 71,
"preview": "._*\nnode_modules\nsrc/home/assets/bundle.debug.js\nwww/assets/bundle.*.js"
},
{
"path": ".vscode/launch.json",
"chars": 552,
"preview": "{\n // 使用 IntelliSense 了解相关属性。 \n // 悬停以查看现有属性的描述。\n // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387\n \"v"
},
{
"path": ".vscode/tasks.json",
"chars": 331,
"preview": "{\n \"tasks\": [\n {\n \"type\": \"shell\",\n \"label\": \"clang build active file\",\n \"command\": \"/usr/bin/clang\","
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2019 EtherDream\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "README.md",
"chars": 748,
"preview": "JsProxy 浏览器端程序\n\n\n# 修改配置\n\n修改 `www/conf.js` 配置,发布 `www` 目录即可。\n\n\n## TODO\n\n* 增加网络错误重试、优先选择空闲节点功能\n\n* 在 SW 中替换 HTML 的 URL 属性,并"
},
{
"path": "src/home/404.html",
"chars": 2424,
"preview": "<!--\n 该页面用于安装 Service Worker\n 用户首次访问时,无论请求哪个资源(包括首页),都返回该页面\n 该页面尽可能精简,兼容所有浏览器\n-->\n<html>\n<head>\n <meta charset=\"utf-"
},
{
"path": "src/home/README.md",
"chars": 380,
"preview": "## 404.html\n\n该页面用于安装 Service Worker。\n\n因为本项目只有一个 404.html,所以访问任意路径都会触发该页面。通过该页面安装 Service Worker 然后自动刷新,后续所有流量都可被 Service"
},
{
"path": "src/home/assets/cors_v1.txt",
"chars": 2861,
"preview": "# HTTP 返回头存在 access-control-allow-origin: * 的站点,不走代理直接连接\n# 收集了部分,实验中...\n\n# google\nssl.google-analytics.com\n\n# [public]\nc"
},
{
"path": "src/home/assets/index_v3.html",
"chars": 3707,
"preview": "<!doctype html>\n<html>\n<head>\n <title>Page Sandbox</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"wi"
},
{
"path": "src/home/build.sh",
"chars": 339,
"preview": "# 压缩 404.html\nhtml-minifier \\\n --collapse-whitespace \\\n --remove-comments \\\n --remove-redundant-attributes \\\n --remo"
},
{
"path": "src/home/conf.js",
"chars": 1795,
"preview": "jsproxy_config({\n // 当前配置的版本(记录在日志中,用于排查问题)\n // 每次修改配置,该值需要增加,否则不会生效。\n // 默认每隔 5 分钟自动下载配置,若想立即验证,可通过隐私模式访问。\n ver: '1"
},
{
"path": "src/home/sw.js",
"chars": 110,
"preview": "jsproxy_config=x=>{__CONF__=x;importScripts(__FILE__=x.assets_cdn+'bundle.debug.js')};importScripts('conf.js')"
},
{
"path": "src/proxy/.eslintrc.json",
"chars": 505,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"es6\": true,\n \"serviceworker\": true\n },\n \"extends\": \"eslint"
},
{
"path": "src/proxy/.package-lock.json",
"chars": 35007,
"preview": "{\n \"name\": \"jsproxy-client\",\n \"version\": \"0.0.1\",\n \"lockfileVersion\": 1,\n \"requires\": true,\n \"dependencies\": {\n "
},
{
"path": "src/proxy/.package.json",
"chars": 352,
"preview": "{\n \"name\": \"jsproxy-client\",\n \"version\": \"0.0.1\",\n \"description\": \"\",\n \"main\": \"boot.js\",\n \"directories\": {\n \"li"
},
{
"path": "src/proxy/README.md",
"chars": 65,
"preview": "浏览器脚本的源文件\n\n# src\n\n源文件目录\n\n\n# debug.sh\n\n开发模式。\n\n# release.sh\n\n发布脚本。\n"
},
{
"path": "src/proxy/debug.sh",
"chars": 74,
"preview": "webpack \\\n --o \"../home/assets/bundle.debug.js\" \\\n --mode development -w"
},
{
"path": "src/proxy/gen-tld/README.md",
"chars": 126,
"preview": "顶级域名后缀生成脚本\n\n# 生成\n\n```bash\nnode gen\n```\n\n# TODO\n\n该列表较大,导致最终 JS 增加上百 KB 的体积。\n\n未考虑 `!` 和 `*.` 开头的域名。\n\n未来考虑将结果保存为单独的文件,JS 运行"
},
{
"path": "src/proxy/gen-tld/gen.js",
"chars": 789,
"preview": "const https = require('https')\nconst fs = require('fs')\nconst {stdout} = process\n\nconst SRC_PATH = 'https://publicsuffix"
},
{
"path": "src/proxy/jsconfig.json",
"chars": 149,
"preview": "{\n \"compilerOptions\": {\n \"checkJs\": true,\n \"lib\": [\"webworker\", \"dom\", \"es2017\"],\n \"target\": \"es2017\"\n },\n \""
},
{
"path": "src/proxy/release.sh",
"chars": 280,
"preview": "DST=../../www/assets\n\nrm $DST/bundle.*.js\n\nwebpack \\\n --o \"$DST/bundle.[hash:8].js\" \\\n --mode production\n\ncd $DST\n\nfor"
},
{
"path": "src/proxy/src/cdn.js",
"chars": 2751,
"preview": "import * as util from './util'\n\n// 暂时先用 jsdelivr 试验。之后换成速度很快、容量更大的免费图床\nconst CDN = 'https://cdn.jsdelivr.net/npm/jsproxy"
},
{
"path": "src/proxy/src/client.js",
"chars": 2561,
"preview": "import * as urlx from './urlx.js'\nimport * as route from './route.js'\nimport * as env from './env.js'\nimport * as hook f"
},
{
"path": "src/proxy/src/cookie.js",
"chars": 7450,
"preview": "import {Database} from './database.js'\n\n/** @type {Set<Cookie>} */\nlet mDirtySet = new Set()\n\n\nfunction Cookie() {\n thi"
},
{
"path": "src/proxy/src/database.js",
"chars": 2636,
"preview": "import {Signal} from './signal.js'\n\n\nexport class Database {\n /**\n * @param {string} name \n */\n constructor(name) "
},
{
"path": "src/proxy/src/env.js",
"chars": 586,
"preview": "export const ENV_PAGE = 1\nexport const ENV_WORKER = 2\nexport const ENV_SW = 3\n\nlet mEnvType = 0\n\nexport function setEnvT"
},
{
"path": "src/proxy/src/fakeloc.js",
"chars": 3996,
"preview": "import * as urlx from \"./urlx\";\n\nconst {\n defineProperty,\n setPrototypeOf,\n} = Object\n\n\nfunction setup(obj, fakeLoc) {"
},
{
"path": "src/proxy/src/hook.js",
"chars": 5551,
"preview": "const {\n getOwnPropertyDescriptor,\n defineProperty,\n apply,\n} = Reflect\n\n\nexport const DROP = {}\n\n/**\n * hook functio"
},
{
"path": "src/proxy/src/index.js",
"chars": 1322,
"preview": "import * as env from \"./env\";\n\n\nfunction pageEnv(win) {\n env.setEnvType(env.ENV_PAGE)\n\n if (win === top) {\n // 开放一个"
},
{
"path": "src/proxy/src/inject.js",
"chars": 1144,
"preview": "import * as path from './path.js'\nimport * as util from \"./util\"\n// import * as jsfilter from './jsfilter.js'\n\nlet mConf"
},
{
"path": "src/proxy/src/jsfilter.js",
"chars": 773,
"preview": "import * as util from './util.js'\n\n\n/**\n * @param {string} code \n */\nexport function parseStr(code) {\n // TODO: parse j"
},
{
"path": "src/proxy/src/msg.js",
"chars": 428,
"preview": "export const PAGE_INFO_PULL = 1\nexport const SW_INFO_PUSH = 2\n\nexport const PAGE_COOKIE_PUSH = 3\nexport const SW_COOKIE_"
},
{
"path": "src/proxy/src/network.js",
"chars": 9820,
"preview": "import * as route from './route.js'\nimport * as cookie from './cookie.js'\nimport * as urlx from './urlx.js'\nimport * as "
},
{
"path": "src/proxy/src/page.js",
"chars": 16071,
"preview": "import * as MSG from './msg.js'\nimport * as route from './route.js'\nimport * as util from './util.js'\nimport * as urlx f"
},
{
"path": "src/proxy/src/path.js",
"chars": 857,
"preview": "\nexport const ROOT = getRootPath() \nexport const HOME = ROOT + 'index.html'\nexport const CONF = ROOT + 'conf.js'\nexport "
},
{
"path": "src/proxy/src/route.js",
"chars": 2066,
"preview": "import * as urlx from './urlx.js'\nimport * as util from './util'\n\n\nlet mConf\nlet mNodeLinesMap\n\n/**\n * @param {number} u"
},
{
"path": "src/proxy/src/signal.js",
"chars": 909,
"preview": "/**\n * Promise 简单封装\n * \n * 封装前\n * ```\n * function get(...) {\n * return new Promise(function(resolve, reject) {\n * "
},
{
"path": "src/proxy/src/storage.js",
"chars": 5431,
"preview": "import * as hook from './hook.js'\nimport * as urlx from './urlx.js'\n\n\nconst {\n apply,\n defineProperty,\n ownKeys,\n ge"
},
{
"path": "src/proxy/src/sw.js",
"chars": 12900,
"preview": "import * as path from './path.js'\nimport * as route from './route.js'\nimport * as urlx from './urlx.js'\nimport * as util"
},
{
"path": "src/proxy/src/tld-data.js",
"chars": 101530,
"preview": "// THIS FILE WAS GENERATED BY './tools/tld/gen.js'\nexport default '0.bg,001www.com,0emm.com,1.bg,12hp.at,12hp.ch,12hp.de"
},
{
"path": "src/proxy/src/tld.js",
"chars": 949,
"preview": "// https://publicsuffix.org/list/effective_tld_names.dat\nimport tldData from './tld-data.js'\nimport {isIPv4} from './uti"
},
{
"path": "src/proxy/src/urlx.js",
"chars": 5389,
"preview": "import * as util from './util.js'\nimport * as env from './env.js'\nimport * as path from './path.js'\nimport * as tld from"
},
{
"path": "src/proxy/src/util.js",
"chars": 1601,
"preview": "const ENC = new TextEncoder()\n\n/**\n * @param {string} str \n */\nexport function strToBytes(str) {\n return ENC.encode(str"
},
{
"path": "www/404.html",
"chars": 1536,
"preview": "<html><head><meta charset=utf-8></head><body id=t><svg xmlns=http://www.w3.org/2000/svg width=100% height=100% viewBox=\""
},
{
"path": "www/assets/README.md",
"chars": 16,
"preview": "该目录的文件可通过 CDN 加速"
},
{
"path": "www/assets/cors_v1.txt",
"chars": 2906,
"preview": "# HTTP 返回头存在 access-control-allow-origin: * 的站点,不走代理直接连接\n# 收集了部分,实验中...\n\n# google\nssl.google-analytics.com\n\n# [public]\nc"
},
{
"path": "www/assets/index_v3.html",
"chars": 3707,
"preview": "<!doctype html>\n<html>\n<head>\n <title>Page Sandbox</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"wi"
},
{
"path": "www/conf.js",
"chars": 1972,
"preview": "jsproxy_config({\n // 当前配置的版本(记录在日志中,用于排查问题)\n // 每次修改配置,该值需要增加,否则不会生效。\n // 默认每隔 5 分钟自动下载配置,若想立即验证,可通过隐私模式访问。\n ver: '1"
},
{
"path": "www/sw.js",
"chars": 113,
"preview": "jsproxy_config=x=>{__CONF__=x;importScripts(__FILE__=x.assets_cdn+'bundle.c33e24c5.js')};importScripts('conf.js')"
}
]
About this extraction
This page contains the full source code of the EtherDream/jsproxy-browser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (242.9 KB), approximately 88.7k tokens, and a symbol index with 178 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.