Repository: docmirror/dev-sidecar
Branch: master
Commit: 5a9fa187a766
Files: 222
Total size: 863.1 KB
Directory structure:
gitextract_p0gsc863/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── 1_BUG_REPORT.md
│ │ ├── 2_STYLE_ISSUE.md
│ │ ├── 3_CONFIG_ISSUES.md
│ │ ├── 4_FEATURE_REQUEST.md
│ │ └── 5_OTHERS.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── build-and-release.yml
│ ├── npm-run-electron.yml
│ └── test-and-upload.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── _script/
│ ├── 0、updateDependencies.bat
│ ├── 1、setupEnv.bat
│ ├── 2、installProject.bat
│ ├── 3、buildAndRun.bat
│ ├── 4.1、runTestCore.bat
│ ├── 4.2、runTestMitmproxy.bat
│ └── 5、generateSetupFile.bat
├── doc/
│ ├── caroot.md
│ ├── linux.md
│ ├── other.md
│ ├── recover.md
│ └── wiki/
│ ├── Home.md
│ ├── 加速服务使用说明.md
│ ├── 各平台安装说明.md
│ └── 解决Github访问不了或速度很慢的问题.md
├── eslint.config.js
├── package.json
├── packages/
│ ├── cli/
│ │ ├── LICENSE
│ │ ├── cli.js
│ │ ├── package.json
│ │ └── src/
│ │ ├── banner.txt
│ │ ├── index.js
│ │ ├── mitmproxy.js
│ │ └── user_config.json5
│ ├── core/
│ │ ├── LICENSE
│ │ ├── index.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── config/
│ │ │ │ ├── index.js
│ │ │ │ ├── local-config-loader.js
│ │ │ │ └── remote_config.json5
│ │ │ ├── config-api.js
│ │ │ ├── event.js
│ │ │ ├── expose.js
│ │ │ ├── index.js
│ │ │ ├── merge.js
│ │ │ ├── modules/
│ │ │ │ ├── index.js
│ │ │ │ ├── plugin/
│ │ │ │ │ ├── git/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── node/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── overwall/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ └── pip/
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── proxy/
│ │ │ │ │ └── index.js
│ │ │ │ └── server/
│ │ │ │ └── index.js
│ │ │ ├── shell/
│ │ │ │ ├── index.js
│ │ │ │ ├── scripts/
│ │ │ │ │ ├── enable-loopback.js
│ │ │ │ │ ├── extra-path/
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── get-npm-env.js
│ │ │ │ │ ├── get-system-env.js
│ │ │ │ │ ├── kill-by-port.js
│ │ │ │ │ ├── set-npm-env.js
│ │ │ │ │ ├── set-system-env.js
│ │ │ │ │ ├── set-system-proxy/
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── refresh-internet.js
│ │ │ │ │ └── setup-ca.js
│ │ │ │ └── shell.js
│ │ │ ├── status.js
│ │ │ └── utils/
│ │ │ ├── util.date.js
│ │ │ ├── util.log-or-console.js
│ │ │ ├── util.log.core.js
│ │ │ ├── util.logger.js
│ │ │ └── util.version.js
│ │ └── test/
│ │ ├── configTest.js
│ │ ├── httpsVerifyTest.js
│ │ ├── macProxyTest.js
│ │ ├── mergeTest.js
│ │ ├── regex.test.js
│ │ ├── requestTest.js
│ │ └── versionTest.js
│ ├── gui/
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── babel.config.js
│ │ ├── build/
│ │ │ ├── icons/
│ │ │ │ └── icon.icns
│ │ │ └── mac/
│ │ │ └── icon.icns
│ │ ├── extra/
│ │ │ ├── icons/
│ │ │ │ └── icon.icns
│ │ │ ├── pac/
│ │ │ │ └── pac.txt
│ │ │ ├── proxy/
│ │ │ │ └── domestic-domain-allowlist.txt
│ │ │ └── scripts/
│ │ │ ├── github.script
│ │ │ ├── google.js
│ │ │ └── tampermonkey.script
│ │ ├── package.json
│ │ ├── pkg/
│ │ │ ├── after-all-artifact-build.js
│ │ │ └── after-pack.js
│ │ ├── public/
│ │ │ └── index.html
│ │ ├── src/
│ │ │ ├── background/
│ │ │ │ └── powerMonitor.js
│ │ │ ├── background.js
│ │ │ ├── bridge/
│ │ │ │ ├── api/
│ │ │ │ │ ├── backend.js
│ │ │ │ │ └── open-enable-loopback.js
│ │ │ │ ├── auto-start/
│ │ │ │ │ ├── backend.js
│ │ │ │ │ └── front.js
│ │ │ │ ├── backend.js
│ │ │ │ ├── error/
│ │ │ │ │ └── front.js
│ │ │ │ ├── file-selector/
│ │ │ │ │ ├── backend.js
│ │ │ │ │ └── front.js
│ │ │ │ ├── front.js
│ │ │ │ ├── mitmproxy.js
│ │ │ │ ├── on-close/
│ │ │ │ │ └── front.js
│ │ │ │ ├── tongji/
│ │ │ │ │ ├── backend.js
│ │ │ │ │ └── front.js
│ │ │ │ └── update/
│ │ │ │ ├── backend.js
│ │ │ │ └── front.js
│ │ │ ├── main.js
│ │ │ ├── utils/
│ │ │ │ ├── util.apppath.js
│ │ │ │ └── util.log.gui.js
│ │ │ └── view/
│ │ │ ├── App.vue
│ │ │ ├── api.js
│ │ │ ├── components/
│ │ │ │ ├── container.vue
│ │ │ │ ├── mock-input.vue
│ │ │ │ ├── setup-ca.vue
│ │ │ │ └── tree-node.vue
│ │ │ ├── composables/
│ │ │ │ └── theme.js
│ │ │ ├── index.js
│ │ │ ├── mixins/
│ │ │ │ └── plugin.js
│ │ │ ├── pages/
│ │ │ │ ├── help.vue
│ │ │ │ ├── index.vue
│ │ │ │ ├── plugin/
│ │ │ │ │ ├── git.vue
│ │ │ │ │ ├── node.vue
│ │ │ │ │ ├── overwall.vue
│ │ │ │ │ └── pip.vue
│ │ │ │ ├── proxy.vue
│ │ │ │ ├── server.vue
│ │ │ │ └── setting.vue
│ │ │ ├── router/
│ │ │ │ ├── index.js
│ │ │ │ └── menu.js
│ │ │ ├── status.js
│ │ │ └── style/
│ │ │ ├── index.scss
│ │ │ └── theme/
│ │ │ └── dark.scss
│ │ └── vue.config.js
│ └── mitmproxy/
│ ├── LICENSE
│ ├── index.js
│ ├── package.json
│ ├── src/
│ │ ├── index.js
│ │ ├── json.js
│ │ ├── lib/
│ │ │ ├── choice/
│ │ │ │ ├── RequestCounter.js
│ │ │ │ └── index.js
│ │ │ ├── dns/
│ │ │ │ ├── base.js
│ │ │ │ ├── https.js
│ │ │ │ ├── index.js
│ │ │ │ ├── preset.js
│ │ │ │ ├── tcp.js
│ │ │ │ ├── tls.js
│ │ │ │ ├── udp.js
│ │ │ │ └── util/
│ │ │ │ └── dns-over-tls.js
│ │ │ ├── interceptor/
│ │ │ │ ├── impl/
│ │ │ │ │ ├── req/
│ │ │ │ │ │ ├── OPTIONS.js
│ │ │ │ │ │ ├── abort.js
│ │ │ │ │ │ ├── baiduOcr.js
│ │ │ │ │ │ ├── cacheRequest.js
│ │ │ │ │ │ ├── proxy.js
│ │ │ │ │ │ ├── redirect.js
│ │ │ │ │ │ ├── requestReplace.js
│ │ │ │ │ │ ├── sni.js
│ │ │ │ │ │ ├── success.js
│ │ │ │ │ │ └── unVerifySsl.js
│ │ │ │ │ └── res/
│ │ │ │ │ ├── AfterOPTIONSHeaders.js
│ │ │ │ │ ├── cacheResponse.js
│ │ │ │ │ ├── responseReplace.js
│ │ │ │ │ └── script.js
│ │ │ │ └── index.js
│ │ │ ├── monkey/
│ │ │ │ └── index.js
│ │ │ ├── proxy/
│ │ │ │ ├── common/
│ │ │ │ │ ├── ProxyHttpAgent.js
│ │ │ │ │ ├── ProxyHttpsAgent.js
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── util.js
│ │ │ │ ├── compatible/
│ │ │ │ │ └── compatible.js
│ │ │ │ ├── index.js
│ │ │ │ ├── middleware/
│ │ │ │ │ ├── InsertScriptMiddleware.js
│ │ │ │ │ ├── overwall.js
│ │ │ │ │ └── source/
│ │ │ │ │ └── pac.js
│ │ │ │ ├── mitmproxy/
│ │ │ │ │ ├── createConnectHandler.js
│ │ │ │ │ ├── createFakeServerCenter.js
│ │ │ │ │ ├── createRequestHandler.js
│ │ │ │ │ ├── createUpgradeHandler.js
│ │ │ │ │ ├── dnsLookup.js
│ │ │ │ │ └── index.js
│ │ │ │ └── tls/
│ │ │ │ ├── CertAndKeyContainer.js
│ │ │ │ ├── FakeServersCenter.js
│ │ │ │ ├── sniUtil.js
│ │ │ │ └── tlsUtils.js
│ │ │ └── speed/
│ │ │ ├── SpeedTester.js
│ │ │ ├── config.js
│ │ │ └── index.js
│ │ ├── options.js
│ │ └── utils/
│ │ ├── util.js
│ │ ├── util.log.server.js
│ │ ├── util.match.js
│ │ └── util.process.js
│ └── test/
│ ├── baiduOcrTest.js
│ ├── dnsSpeedTest.js
│ ├── dnsTest-abroad-doh-sni.mjs
│ ├── dnsTest-abroad-dot-sni.mjs
│ ├── dnsTest-abroad.mjs
│ ├── dnsTest.mjs
│ ├── lodashTest.js
│ ├── matchTest.js
│ ├── matchUtilTest.js
│ ├── monkeyTest.js
│ ├── pacTest.js
│ ├── proxyTest.js
│ ├── responseReplaceTest.js
│ ├── sha256Test.js
│ └── utilTest.js
├── pnpm-workspace.yaml
└── test/
├── test.js
└── testDns.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/1_BUG_REPORT.md
================================================
---
name: 问题上报
about: 如果你在使用过程中发现问题,请使用此模板。
labels: Bug
---
- [ ] 你是否在现有 [Issue列表](/docmirror/dev-sidecar/issues) 中搜索过相同问题,但未找到?
### Ⅰ. 请说明操作系统及DS的版本号:
1. 操作系统:?
2. DS版本号:?
### Ⅱ. 问题描述:
### Ⅲ. 期望的结果:
### Ⅳ. 如何复现问题?
1. xxx
2. xxx
3. xxx
### Ⅴ. 请提供相关的错误日志,尽可能的详细:(日志文件在 `${user.home}/.dev-sidecar/logs/` 目录下)
点击查看日志
```log
```
### Ⅵ. 有必要时,请提供 `${user.home}/.dev-sidecar/running.json` 文件内容:
点击查看运行参数
```json
```
================================================
FILE: .github/ISSUE_TEMPLATE/2_STYLE_ISSUE.md
================================================
---
name: 样式问题
about: 如果你发现了一些页面样式问题,请使用此模板。
labels: Style Issue
---
- [ ] 你是否在现有 [Issue列表](/docmirror/dev-sidecar/issues) 中搜索过相同问题,但未找到?
### Ⅰ. 请说明操作系统及DS的版本号:
1. 操作系统:?
2. DS版本号:?
### Ⅱ. 样式问题描述:
### Ⅲ. 样式问题截图:
================================================
FILE: .github/ISSUE_TEMPLATE/3_CONFIG_ISSUES.md
================================================
---
name: 配置问题
about: 如果你不知道如何配置DS来访问某个网站,请使用这个模板。
labels: Config Issue
---
### Ⅰ. 你对哪个功能的配置不了解?
- [ ] 拦截设置:
- [ ] redirect
- [ ] proxy
- [ ] sni
- [ ] success
- [ ] abort
- [ ] cache
- [ ] options
- [ ] script
- [ ] requestReplace
- [ ] responseReplace
- [ ] DNS设置和IP测速
- [ ] 系统代理
- [ ] 远程配置
- [ ] 应用:
- [ ] NPM加速
- [ ] Git代理
- [ ] PIP加速
- [ ] 增强功能
### Ⅱ. 请详细描述你的问题:
### Ⅲ. 有必要时,请提供 `${user.home}/.dev-sidecar/running.json` 文件内容:
点击查看运行参数
```json
```
================================================
FILE: .github/ISSUE_TEMPLATE/4_FEATURE_REQUEST.md
================================================
---
name: 提新需求
about: 如果你想提出一个新需求,请使用此模板。
labels: Feature Request
---
### Ⅰ. 请描述你想要的新功能:
### Ⅱ. 请描述你心目中新功能的样子:
### Ⅲ. 你希望该新功能修复哪个issue?
================================================
FILE: .github/ISSUE_TEMPLATE/5_OTHERS.md
================================================
---
name: 其他问题
about: 如果不是以上问题,请使用此模板。
---
### 请详细描述你的问题、需求或建议:
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### Ⅰ. 描述此PR的作用:
### Ⅱ. 此PR修复了哪个issue吗?
### Ⅲ. 界面变化截屏
================================================
FILE: .github/workflows/build-and-release.yml
================================================
name: Build And Release
on:
push:
branches:
- release*
jobs:
# job 1
build-and-upload:
runs-on: ${{ matrix.os }}-latest
env:
ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
ELECTRON_BUILDER_CACHE: ${{ github.workspace }}/.cache/electron-builder
strategy:
fail-fast: false
matrix:
os:
- windows
- ubuntu
- macos
node:
- 22
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: 'Setup Node.js "${{ matrix.node }}.x" environment'
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
registry-url: https://npm.pkg.github.com/
cache: pnpm
- name: Setup Python environment (Mac) Because of electron-builder install-app-deps requires Python setup tools
if: matrix.os == 'macos'
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Get package info
id: package-info
uses: luizfelipelaviola/get-package-info@v1
with:
path: ./packages/mitmproxy
- name: Print
run: |
echo "version = ${{ steps.package-info.outputs.version }}";
echo "github.ref_type = ${{ github.ref_type }}";
echo "github.ref = ${{ github.ref }}";
echo "github.ref_name = ${{ github.ref_name }}";
- name: 'npm -v | pnpm -v | python --version'
run: |
echo "======================================================================";
echo "npm -v";
echo "--------------------";
npm -v;
echo "======================================================================";
echo "pnpm -v";
echo "--------------------";
pnpm -v;
echo "======================================================================";
echo "python --version";
echo "--------------------";
python --version;
- name: Setup electron cahce
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cache/electron
key: ${{ runner.os }}-electron-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-cache-
- name: Setup electron-builder cahce
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cache/electron-builder
key: ${{ runner.os }}-electron-builder-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-builder-cache-
- name: "'pnpm install' Because we need to install optional dependencies"
run: |
echo "======================================================================";
dir || ls -lah;
echo "======================================================================";
echo "pnpm install";
echo "--------------------";
pnpm install;
- name: 'test packages/core'
run: |
cd packages/core;
pnpm run test;
- name: 'test packages/mitmproxy'
run: |
cd packages/mitmproxy;
pnpm run test;
- name: 'npm run electron:build'
run: |
echo "======================================================================";
echo "cd packages/gui";
echo "--------------------";
cd packages/gui;
dir || ls -lah;
echo "======================================================================";
echo "npm run electron:build";
echo "--------------------";
npm run electron:build;
- name: 'Print dir "packages/gui/dist_electron/"'
run: |
echo "======================================================================";
echo "cd packages/gui/dist_electron";
echo "--------------------";
cd packages/gui/dist_electron;
dir || ls -lah;
# Rename artifacts
- name: 'Rename artifacts - Windows'
if: ${{ matrix.os == 'windows' }}
run: |
cd packages/gui/dist_electron;
ren DevSidecar-${{ steps.package-info.outputs.version }}-x64.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe;
ren DevSidecar-${{ steps.package-info.outputs.version }}-ia32.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe;
ren DevSidecar-${{ steps.package-info.outputs.version }}-arm64.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe;
ren DevSidecar-${{ steps.package-info.outputs.version }}.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe;
dir;
- name: 'Rename artifacts - Linux'
if: ${{ matrix.os == 'ubuntu' }}
run: |
cd packages/gui/dist_electron;
mv DevSidecar-${{ steps.package-info.outputs.version }}-amd64.deb DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb;
mv DevSidecar-${{ steps.package-info.outputs.version }}-x86_64.AppImage DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage;
mv DevSidecar-${{ steps.package-info.outputs.version }}-x64.tar.gz DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz;
#-------------------------------------------------------------------------------------------------------------------------
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.deb DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb;
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.AppImage DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage;
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.tar.gz DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz;
#-------------------------------------------------------------------------------------------------------------------------
mv DevSidecar-${{ steps.package-info.outputs.version }}-armv7l.deb DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb;
mv DevSidecar-${{ steps.package-info.outputs.version }}-armv7l.AppImage DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage;
mv DevSidecar-${{ steps.package-info.outputs.version }}-armv7l.tar.gz DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz;
ls -lah;
- name: 'Rename artifacts - macOS'
if: ${{ matrix.os == 'macos' }}
run: |
cd packages/gui/dist_electron;
mv DevSidecar-${{ steps.package-info.outputs.version }}-x64.dmg DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg;
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.dmg DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg;
mv DevSidecar-${{ steps.package-info.outputs.version }}-universal.dmg DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg;
ls -lah;
#region Upload artifacts - Windows
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe'
if-no-files-found: error
#endregion Upload artifacts - Windows
#region Upload artifacts - Linux
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz'
if-no-files-found: error
#-------------------------------------------------------------------------------------------------------------------------
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz'
if-no-files-found: error
#-------------------------------------------------------------------------------------------------------------------------
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz'
if-no-files-found: error
#endregion Upload artifacts - Linux
# Upload artifacts - macOS
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'macos' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'macos' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'macos' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg'
if-no-files-found: error
# job 2
download-and-release:
runs-on: ubuntu-latest
needs:
- build-and-upload
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Get package info
id: package-info
uses: luizfelipelaviola/get-package-info@v1
with:
path: ./packages/mitmproxy
- name: 'Make "release" dir'
run: mkdir release
# Download artifacts
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz'
path: release
#-------------------------------------------------------------------------------------------------------------------------
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz'
path: release
#-------------------------------------------------------------------------------------------------------------------------
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg'
path: release
- name: 'Download DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg'
uses: actions/download-artifact@v4.1.8
with:
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg'
path: release
- name: 'Print files from "release" dir'
run: |
ls -lah release;
- name: Create a draft release
uses: wangliang181230/github-action-ghr@master
env:
GITHUB_TOKEN: ${{ github.token }}
GHR_PATH: release/
GHR_TITLE: ${{ github.ref_name }}
GHR_REPLACE: true
GHR_DRAFT: true
================================================
FILE: .github/workflows/npm-run-electron.yml
================================================
name: npm run electron
on:
push:
branches:
- run*
- test*
- release*
paths-ignore:
- '_script/**'
- 'doc/**'
- '**/*.md'
- '**/.gitignore'
- '**/LICENSE'
jobs:
npm-run-electron:
runs-on: ${{ matrix.os }}-latest
env:
ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
ELECTRON_BUILDER_CACHE: ${{ github.workspace }}/.cache/electron-builder
strategy:
fail-fast: false
matrix:
os:
- windows
- ubuntu
- macos
node:
- 22
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: 'Setup Node.js "${{ matrix.node }}.x" environment'
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
registry-url: https://npm.pkg.github.com/
cache: pnpm
- name: Setup Python environment (Mac) Because of electron-builder install-app-deps requires Python setup tools
if: matrix.os == 'macos'
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Print
run: |
echo "github.ref_type = ${{ github.ref_type }}";
echo "github.ref = ${{ github.ref }}";
echo "github.ref_name = ${{ github.ref_name }}";
- name: 'npm -v | pnpm -v | python --version'
run: |
echo "======================================================================";
echo "npm -v";
echo "--------------------";
npm -v;
echo "======================================================================";
echo "pnpm -v";
echo "--------------------";
pnpm -v;
echo "======================================================================";
echo "python --version";
echo "--------------------";
python --version;
- name: Setup electron cahce
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cache/electron
key: ${{ runner.os }}-electron-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-cache-
- name: Setup electron-builder cahce
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cache/electron-builder
key: ${{ runner.os }}-electron-builder-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-builder-cache-
- name: pnpm install
run: |
echo "======================================================================";
dir || ls -lah;
echo "======================================================================";
echo "pnpm install";
echo "--------------------";
pnpm install;
- name: npm run electron
run: |
echo "======================================================================";
echo "cd packages/gui";
echo "--------------------";
cd packages/gui;
dir || ls -lah;
echo "======================================================================";
echo "npm run electron";
echo "--------------------";
npm run electron;
================================================
FILE: .github/workflows/test-and-upload.yml
================================================
name: Test And Upload
on:
push:
branches:
- master
- 1.x
- develop
- test*
paths-ignore:
- '_script/**'
- 'doc/**'
- '**/*.md'
- '**/.gitignore'
- '**/LICENSE'
pull_request:
branches:
- master
- develop
- 1.x
paths-ignore:
- '_script/**'
- 'doc/**'
- '**/*.md'
- '**/.gitignore'
- '**/LICENSE'
jobs:
test-and-upload:
runs-on: ${{ matrix.os }}-latest
env:
ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
ELECTRON_BUILDER_CACHE: ${{ github.workspace }}/.cache/electron-builder
strategy:
fail-fast: false
matrix:
os:
- windows
- ubuntu
- macos
node:
- 22
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: 'Setup Node.js "${{ matrix.node }}.x" environment'
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
registry-url: https://npm.pkg.github.com/
cache: pnpm
- name: Setup Python environment (Mac) Because of electron-builder install-app-deps requires Python setup tools
if: matrix.os == 'macos'
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Get package info
id: package-info
uses: luizfelipelaviola/get-package-info@v1
with:
path: ./packages/mitmproxy
- name: Print
run: |
echo "version = ${{ steps.package-info.outputs.version }}";
echo "github.ref_type = ${{ github.ref_type }}";
echo "github.ref = ${{ github.ref }}";
echo "github.ref_name = ${{ github.ref_name }}";
- name: 'npm -v | pnpm -v | python --version'
run: |
echo "======================================================================";
echo "npm -v";
echo "--------------------";
npm -v;
echo "======================================================================";
echo "pnpm -v";
echo "--------------------";
pnpm -v;
echo "======================================================================";
echo "python --version";
echo "--------------------";
python --version;
- name: Setup electron cahce
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cache/electron
key: ${{ runner.os }}-electron-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-cache-
- name: Setup electron-builder cahce
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cache/electron-builder
key: ${{ runner.os }}-electron-builder-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-builder-cache-
- name: "'pnpm install' Because we need to install optional dependencies"
run: |
echo "======================================================================";
dir || ls -lah;
echo "======================================================================";
echo "pnpm install";
echo "--------------------";
pnpm install;
- name: 'test packages/core'
run: |
cd packages/core;
pnpm run test;
- name: 'test packages/mitmproxy'
run: |
cd packages/mitmproxy;
pnpm run test;
- name: 'npm run electron:build'
run: |
echo "======================================================================";
echo "cd packages/gui";
echo "--------------------";
cd packages/gui;
dir || ls -lah;
echo "======================================================================";
echo "npm run electron:build";
echo "--------------------";
npm run electron:build;
- name: 'Print dir "packages/gui/dist_electron/"'
run: |
echo "======================================================================";
echo "cd packages/gui/dist_electron";
echo "--------------------";
cd packages/gui/dist_electron;
dir || ls -lah;
# Rename artifacts
- name: 'Rename artifacts - Windows'
if: ${{ matrix.os == 'windows' }}
run: |
cd packages/gui/dist_electron;
ren DevSidecar-${{ steps.package-info.outputs.version }}-x64.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe;
ren DevSidecar-${{ steps.package-info.outputs.version }}-ia32.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe;
ren DevSidecar-${{ steps.package-info.outputs.version }}-arm64.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe;
ren DevSidecar-${{ steps.package-info.outputs.version }}.exe DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe;
dir;
- name: 'Rename artifacts - Linux'
if: ${{ matrix.os == 'ubuntu' }}
run: |
cd packages/gui/dist_electron;
mv DevSidecar-${{ steps.package-info.outputs.version }}-amd64.deb DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb;
mv DevSidecar-${{ steps.package-info.outputs.version }}-x86_64.AppImage DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage;
mv DevSidecar-${{ steps.package-info.outputs.version }}-x64.tar.gz DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz;
#-------------------------------------------------------------------------------------------------------------------------
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.deb DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb;
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.AppImage DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage;
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.tar.gz DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz;
#-------------------------------------------------------------------------------------------------------------------------
mv DevSidecar-${{ steps.package-info.outputs.version }}-armv7l.deb DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb;
mv DevSidecar-${{ steps.package-info.outputs.version }}-armv7l.AppImage DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage;
mv DevSidecar-${{ steps.package-info.outputs.version }}-armv7l.tar.gz DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz;
ls -lah;
- name: 'Rename artifacts - macOS'
if: ${{ matrix.os == 'macos' }}
run: |
cd packages/gui/dist_electron;
mv DevSidecar-${{ steps.package-info.outputs.version }}-x64.dmg DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg;
mv DevSidecar-${{ steps.package-info.outputs.version }}-arm64.dmg DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg;
mv DevSidecar-${{ steps.package-info.outputs.version }}-universal.dmg DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg;
ls -lah;
#region Upload artifacts - Windows
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-x64.exe'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-ia32.exe'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-arm64.exe'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'windows' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-windows-universal.exe'
if-no-files-found: error
#endregion Upload artifacts - Windows
#region Upload artifacts - Linux
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-amd64.deb'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-x86_64.AppImage'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-x64.tar.gz'
if-no-files-found: error
#-------------------------------------------------------------------------------------------------------------------------
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.deb'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.AppImage'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-arm64.tar.gz'
if-no-files-found: error
#-------------------------------------------------------------------------------------------------------------------------
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.deb'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.AppImage'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'ubuntu' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-linux-armv7l.tar.gz'
if-no-files-found: error
#endregion Upload artifacts - Linux
# Upload artifacts - macOS
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'macos' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-x64.dmg'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'macos' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-arm64.dmg'
if-no-files-found: error
- name: 'Upload DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg'
uses: actions/upload-artifact@v4.4.0
if: ${{ matrix.os == 'macos' }}
with:
path: packages/gui/dist_electron/DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg
name: 'DevSidecar-${{ steps.package-info.outputs.version }}-macos-universal.dmg'
if-no-files-found: error
================================================
FILE: .gitignore
================================================
# IntelliJ project files
.idea
*.iml
# vscode settings files
.vscode
# Mac
.DS_Store
# Node files
node_modules/
*.lock
package-lock.json
# Other files
out
gen
*.log
*.lnk
================================================
FILE: .npmrc
================================================
shamefully-hoist=true
================================================
FILE: LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: README.md
================================================
# dev-sidecar
开发者边车,命名取自service-mesh的service-sidecar,意为为开发者打辅助的边车工具(以下简称ds)
通过本地代理的方式将https请求代理到一些国内的加速通道上
[](https://www.star-history.com/#docmirror/dev-sidecar&type=date&legend=top-left)
> Gitee上的同步项目已被封禁,请认准本项目唯一官方仓库地址[https://github.com/docmirror/dev-sidecar](https://github.com/docmirror/dev-sidecar) 【狗头保命】
>
> 我将继续奋战在开源一线,为社区贡献更多更好的开源项目。
>
> 感兴趣的可以关注我的主页 [【github】](https://github.com/greper) [【gitee】](https://gitee.com/greper)
## 打个广告
> [https://github.com/certd/certd](https://github.com/certd/certd)
>
> 我的开源证书管理工具项目,全自动申请和部署证书,有需求的可以去试试,帮忙点个star
## 重要提醒
> ------------------------------重要提醒1---------------------------------
>
> 注意:由于electron无法监听windows的关机事件,开着ds情况下直接重启电脑,会导致无法上网,你可以手动启动ds即可恢复网络,你也可以将ds设置为开机自启。
>
> 关于此问题的更多讨论请前往:[https://github.com/docmirror/dev-sidecar/issues/109](https://github.com/docmirror/dev-sidecar/issues/109)
>
> 注:此问题已在 `1.8.9` 版本中得到解决。
> ------------------------------重要提醒2---------------------------------
>
> 注意:本应用启动会自动修改系统代理,所以会与其他代理软件有冲突,一起使用时请谨慎使用。
>
> 与Watt Toolkit(原Steam++)共用时,请以hosts模式启动Watt Toolkit
>
> 与TUN网卡模式运行的游戏加速器可以共用
>
> 本应用主要目的在于直连访问github,如果你已经有飞机了,那建议还是不要用这个自行车(ds)了
## 一、 特性
### 1.1、 dns优选(解决\*\*\*污染问题)
- 根据网络状况智能解析最佳域名ip地址,获取最佳网络速度
- 解决一些网站和库无法访问或访问速度慢的问题
- 建议遇到打开比较慢的国外网站,可以优先尝试将该域名添加到dns设置中(注意:被\*\*\*封杀的无效)
### 1.2、 请求拦截
- 拦截打不开的网站,代理到加速镜像站点上去。
- 可配置多个镜像站作为备份
- 具备测速机制,当访问失败或超时之后,自动切换到备用站点,使得目标服务高可用
### 1.3、 github加速
- github 直连加速 (通过修改sni实现,感谢 [fastGithub](https://github.com/dotnetcore/FastGithub) 提供的思路)
- release、source、zip下载加速
- clone 加速
- 头像加速
- 解决readme中图片引用无法加载的问题
- gist.github.com 加速
- 解决git push 偶尔失败需要输入账号密码的问题(fatal: TaskCanceledException encountered / fatal: HttpRequestException encountered)
- raw/blame加速
> 以上部分功能通过 `X.I.U` 的油猴脚本实现, 以下是仓库和脚本下载链接,大家可以去支持一下。
>
> - [https://github.com/XIU2/UserScript](https://github.com/XIU2/UserScript)
>
> - [https://greasyfork.org/scripts/412245](https://greasyfork.org/scripts/412245)
>
> 由于此脚本在ds中是打包在本地的,更新会不及时,你可以直接通过浏览器安装油猴插件使用此脚本,从而获得最新更新(ds本地的可以通过 `加速服务->基本设置->启用脚本` 进行关闭)。
### 1.4、 Stack Overflow 加速
- 将ajax.google.com代理到加速CDN上
- recaptcha 图片验证码加速
### 1.5、 npm加速
- 支持开启npm代理
- 官方与淘宝npm registry一键切换
- 某些npm install的时候,并且使用cnpm也无法安装时,可以尝试开启npm代理再试
**_安全警告_**:
- 请勿使用来源不明的服务/远程配置地址,有隐私和账号泄露风险
- 本应用及服务/默认远程配置端承诺不收集任何信息。介意者请使用安全模式。
## 二、快速开始
支持windows、Mac、Linux(Ubuntu)
### 2.1、DevSidecar桌面应用
#### 1)下载安装包
- release下载
[Github Release](https://github.com/docmirror/dev-sidecar/releases)
> Windows: 请选择DevSidecar-x.x.x-windows-universal.exe
>
> Mac: 请选择DevSidecar-x.x.x-macos-universal.dmg
>
> Debian系及其他支持deb安装包的Linux: 请选择DevSidecar-x.x.x-linux-[架构].deb
>
> 其他Linux: 请选择DevSidecar-x.x.x-linux-[架构].AppImage (未做测试,不保证能用)
> linux安装说明请参考 [linux安装文档](./doc/linux.md)
> 注意:由于没有买应用证书,所以应用在下载安装时会有“未知发行者”等安全提示,选择保留即可。
#### 2)安装后打开
界面应大致如下图所示:
> 注意:mac版安装需要在“系统偏好设置->安全性与隐私->通用”中解锁并允许应用安装

#### 3)安装根证书
第一次打开会提示安装证书,根据提示操作即可
更多有关根证书的说明,请参考 [为什么要安装根证书?](./doc/caroot.md)
> 根证书是本地随机生成的,所以不用担心根证书的安全问题(本应用不收集任何用户信息)
>
> 你也可以在加速服务设置中自定义根证书(PEM格式的证书与私钥)
> 火狐浏览器需要[手动安装证书](#3火狐浏览器火狐浏览器不走系统的根证书需要在选项中添加根证书)
#### 4)开始加速吧
去试试打开github、huggingface、docker hub吧
### 2.2、开启前 vs 开启后
| | 开启前 | 开启后 |
| -------- | ------------------------------ | ----------------------------------------------- |
| 头像 |  |  |
| clone |  |  |
| zip 下载 |  | 秒下的,实在截不到速度的图 |
## 三、模式说明
### 3.1、安全模式
- 此模式:关闭拦截、关闭增强、不使用远程配置、开启dns优选、开启测速
- 最安全,无需安装证书,可以在浏览器地址栏左侧查看域名证书
- 功能也最弱,只有特性1,相当于查询github的国外ip,手动改hosts一个意思。
- github的可访问性不稳定,取决于IP测速,如果有绿色ip存在,就 `有可能` 可以直连访问。

### 3.2、默认模式
- 此模式:开启拦截、关闭增强、使用远程配置、开启dns优选、开启测速
- 需要安装证书,通过修改sni直连访问github
- 功能上包含特性1/2/3/4。
## 四、 最佳实践
- 把dev-sidecar一直开着就行了
- 建议遇到打开比较慢的国外网站,可以尝试将该域名添加到dns设置中(注意:被\*\*\*封杀的无效)
### 其他加速
#### 1)git clone 加速
- 方式1:快捷复制:
> 开启脚本支持,然后在复制clone链接下方,即可复制到加速链接
- 方式2:
> 1. 使用方式:用实际的名称替换 `{}` 的内容,即可加速clone [https://hub.fastgit.org/{username}/{reponame}.git](https://hub.fastgit.org/%7Busername%7D/%7Breponame%7D.git)
> 2. clone 出来的 remote "origin" 为fastgit的地址,需要手动改回来
> 3. 你也可以直接使用他们的clone加速工具 [fgit-go](https://github.com/FastGitORG/fgit-go)
#### 2)`github.com` 的镜像网站(注意:部分镜像网站不能登录)
> 1. [hub.fastgit.org](https://hub.fastgit.org/) (2024/11/18:这个好像失效了?)
> 2. [github.com.cnpmjs.org](https://github.com.cnpmjs.org/) 这个很容易超限(2024/11/18:这个好像失效了?)
> 3. [dgithub.xyz](https://dgithub.xyz/)
## 五、api
### 5.1、拦截配置
没有配置域名的不会拦截,其他根据配置进行拦截处理。
在【加速服务-拦截设置】中配置,格式如下:(更多内容参见[wiki](https://github.com/docmirror/dev-sidecar/wiki/%E5%8A%A0%E9%80%9F%E6%9C%8D%E5%8A%A1%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E))
```jsonc
{
// 要拦截的域名
'github.com': {
// 需要拦截url的正则表达式
'/.*/.*/releases/download/': {
// 拦截类型
// redirect: url, // 临时重定向(url会变,一些下载资源可以通过此方式配置)
// proxy: url, // 代理(url不会变,没有跨域问题)
// abort: true, // 取消请求(适用于被***封锁的资源,找不到替代,直接取消请求,快速失败,节省时间)
// success: true, // 直接返回成功请求(某些请求不想发出去,可以伪装成功返回)
// cacheDays: 1, // GET请求的使用缓存,单位:天(常用于一些静态资源)
// options: true, // OPTIONS请求直接返回成功请求(该功能存在一定风险,请谨慎使用)
// optionsMaxAge: 2592000, // OPTIONS请求缓存时间,默认:2592000(一个月)
redirect: 'download.fastgit.org'
},
'.*': {
proxy: 'github.com',
sni: 'baidu.com' // 修改sni,规避***握手拦截
}
},
'ajax.googleapis.com': {
'.*': {
proxy: 'ajax.loli.net', // 代理请求,url不会变
backup: ['ajax.proxy.ustclug.org'], // 备份,当前代理请求失败后,将会切换到备用地址
test: 'ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js',
replace: '/(.*)/xxx'// 当加速地址的链接和原链接不是完全相同时,可以通过正则表达式replace,此时proxy通过$1$2来重组url, proxy:'ajax.loli.net/xxx/$1'
}
},
'clients*.google.com': {
'.*': {
abort: true // 取消请求,被***封锁的资源,找不到替代,直接取消请求,快速失败,节省时间
}
}
}
```
### 5.2、DNS优选配置
某些域名解析出来的ip会无法访问,(比如api.github.com会被解析到新加坡的ip上,新加坡的服务器在上午挺好,到了晚上就卡死,基本不可用)
通过从dns上获取ip列表,切换不同的ip进行尝试,最终会挑选到一个最快的ip(该功能需要事先配置好所用DNS),更多说明参见[wiki](https://github.com/docmirror/dev-sidecar/wiki/%E5%8A%A0%E9%80%9F%E6%9C%8D%E5%8A%A1%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
```json
{
"dns": {
"mapping": {
"api.github.com": "cloudflare", // "解决push的时候需要输入密码的问题",
"gist.github.com": "cloudflare", // 解决gist无法访问的问题
"*.githubusercontent.com": "cloudflare" // 解决github头像经常下载不到的问题
}
}
}
```
注意:暂时只支持IPv4的解析
## 六、问题排查
### 6.1、dev-sidecar的前两个开关没有处于打开状态
1. 尝试将开关按钮手动打开
2. 请尝试右键dev-sidecar图标,点退出。再重新打开
3. 如果还不行,请将日志发送给作者
如果是mac系统,可能是下面的原因
#### 1)Mac系统使用时,首页的系统代理开关无法打开
出现这个问题可能是没有开启系统代理命令的执行权限
```
networksetup -setwebproxy 'WiFi' 127.0.0.1 31181
#看是否有如下错误提示
** Error: Command requires admin privileges.
```
如果有上面的错误提示,请尝试如下方法:
> 取消访问偏好设置需要管理员密码
>
> 系统偏好设置—>安全性与隐私—> 通用—> 高级—> 访问系统范围的偏好设置需要输入管理员密码(取消勾选)
### 6.2、没有加速效果
1. 本应用默认仅开启https加速,一般足够覆盖需求。
如果你访问的是仅支持http协议的网站,请手动在【系统代理】中打开【代理HTTP请求】
2. 检查浏览器是否装了什么插件,与ds有冲突
3. 检查是否安装了其他代理软件,与ds有冲突
4. 请确认浏览器的代理设置为使用IE代理/或者使用系统代理状态
5. 可以尝试换个浏览器试试
6. 请确认网络代理设置处于勾选状态
正常情况下ds在“系统代理”开关打开时,会自动设置系统代理。
### 6.3、浏览器打开提示证书不受信任

一般是证书安装位置不对,重新安装根证书后,重启浏览器
#### 1)windows: 请确认证书已正确安装在“本地计算机-将所有的证书都放入下列存储:受信任的根证书颁发机构”下
#### 2)mac: 请确认证书已经被安装并已经设置信任
#### 3)火狐浏览器:火狐浏览器不走系统的根证书,需要在选项中添加根证书
1. 火狐浏览器->选项->隐私与安全->证书->查看证书

2. 证书颁发机构->导入
3. 选择证书文件 `C:\Users(用户)\Administrator(你的账号)\.dev-sidecar\dev-sidecar.ca.crt`(Mac或linux为 `~/.dev-sidecar` 目录)

4. 勾选信任由此证书颁发机构来标识网站,确定即可

### 6.4、打开github显示连接超时
```html
DevSidecar Warning: Error: www.github.com:443, 代理请求超时
```
1. 检查测速界面github.com是否有ip ,如果没有ip,则可能是由于你的网络提供商封锁了dns服务商的ip(试试能否ping通:1.1.1.1 / 9.9.9.9 )
2. 如果是安全模式,则是因为不稳定导致的,等一会再刷新试试
3. 如果是增强模式,则是由于访问人数过多,正常现象
### 6.5、查看日志是否有报错
如果还是不行,请在下方加官方QQ群或提issue,附上服务日志(server.log)以便进行分析
日志打开方式:加速服务->右边日志按钮->打开日志文件夹

### 6.6、某些原本可以打开的网站打不开了
1. 可以尝试关闭pac
2. 可以将域名加入白名单
### 6.7、应用意外关闭导致没有网络了
应用开启后会自动修改系统代理设置,正常退出会自动关闭系统代理
当应用意外关闭时,可能会因为没有将系统代理恢复,从而导致完全无法上网。
对于此问题有如下几种解决方案可供选择:
1. 重新打开应用即可(右键应用托盘图标可完全退出,将会正常关闭系统代理设置)
2. 如果应用被卸载了,此时需要[手动关闭系统代理设置](./doc/recover.md)
3. 如果你是因为开着ds的情况下重启电脑导致无法上网,你可以设置ds为开机自启
### 6.8、卸载应用后上不了网,git请求不了
如果你在卸载应用前,没有正常退出app,就有可能无法上网。请按如下步骤操作恢复您的网络:
1、关闭系统代理设置,参见:[手动关闭系统代理设置](./doc/recover.md)
2、执行下面的命令关闭git的代理设置(如果你开启过 `Git.exe代理` 的开关)
```shell
git config --global --unset http.proxy
git config --global --unset https.proxy
git config --global --unset http.sslVerify
```
3、执行下面的命令关闭npm的代理设置(如果你开启过npm加速的开关)
```shell
npm config delete proxy
npm config delete https-proxy
```
### 6.9、其他问题
请查阅[wiki](https://github.com/docmirror/dev-sidecar/wiki)
也可以查阅[有文档tag的issue](https://github.com/docmirror/dev-sidecar/issues?q=is%3Aissue%20label%3ADocumentation),它们被开发者认证为相当于文档级别的参考issue。
## 七、在其他程序使用
- [java程序使用](./doc/other.md#Java程序使用)
## 八、贡献代码
### 8.1、准备环境
#### 1)安装 `nodejs`
推荐安装 nodejs `22.x.x` 的版本,其他版本未做测试
#### 2)安装 `pnpm`
运行如下命令即可安装所需依赖:
```shell
npm install -g pnpm --registry=https://registry.npmmirror.com
```
### 8.2、开发调试模式启动
运行如下命令即可开发模式启动
```shell
# 拉取代码
git clone https://github.com/docmirror/dev-sidecar
cd dev-sidecar
# 注意不要使用 `npm install` 来安装依赖,因为 `pnpm` 会自动安装依赖
pnpm install
# 运行DevSidecar
cd packages/gui
npm run electron
```
> 如果electron依赖包下载不动,可以开启ds的npm加速
### 8.3、打包成可执行文件
```shell
# 先执行上面的步骤,然后运行如下命令打包成可执行文件
npm run electron:build
```
### 8.4、提交pr
如果你想将你的修改贡献出来,请提交pr
## 九、联系作者
欢迎bug反馈,需求建议,技术交流等
加官方QQ群(请备注dev-sidecar,或简称DS)
- QQ 1群:390691483,人数:499 / 500(满)
- QQ 2群:667666069,人数:500 / 500(满)
- QQ 3群:419807815,人数:493 / 500(满)
- QQ 4群:[438148299](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=i_NCBB5f_Bkm2JsEV1tLs2TkQ79UlCID&authKey=nMsVJbJ6P%2FGNO7Q6vsVUadXRKnULUURwR8zvUZJnP3IgzhHYPhYdcBCHvoOh8vYr&noverify=0&group_code=438148299),人数:700 / 1000
- QQ 5群:[767622917](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=nAWi_Rxj7mM4Unp5LMiatmUWhGimtbcB&authKey=aswmlWGjbt3GIWXtvjB2GJqqAKuv7hWjk6UBs3MTb%2Biyvr%2Fsbb1kA9CjF6sK7Hgg&noverify=0&group_code=767622917),人数:200 / 200(new)
## 十、求star
我的其他项目求star
- [fast-crud](https://github.com/fast-crud/fast-crud) : 开发crud快如闪电
- [certd](https://github.com/certd/certd) : 让你的证书永不过期
- [trident-sync](https://github.com/handsfree-work/trident-sync) : 二次开发项目同步升级工具
## 十一、感谢
本项目使用lerna包管理工具
[](https://lerna.js.org/)
本项目参考如下开源项目
- [node-mitmproxy](https://github.com/wuchangming/node-mitmproxy)
- [ReplaceGoogleCDN](https://github.com/justjavac/ReplaceGoogleCDN)
特别感谢
- [github增强油猴脚本](https://greasyfork.org/zh-CN/scripts/412245-github-%E5%A2%9E%E5%BC%BA-%E9%AB%98%E9%80%9F%E4%B8%8B%E8%BD%BD) 本项目部分加速功能完全复制该脚本。
- [中国域名白名单](https://github.com/pluwen/china-domain-allowlist),本项目的系统代理排除域名功能中,使用了该白名单。
本项目部分加速资源由如下组织提供
- [FastGit UK](https://fastgit.org/)
================================================
FILE: _script/0、updateDependencies.bat
================================================
node -v
# 安装ncu
# npm install -g npm-check-updates
cd ../packages/core
ncu -u
# cd ../packages/gui
# ncu -u
# cd ../packages/mitmproxy
# ncu -u
================================================
FILE: _script/1、setupEnv.bat
================================================
node -v
cd ../
npm install -g pnpm --registry=https://registry.npmmirror.com
================================================
FILE: _script/2、installProject.bat
================================================
node -v
cd ../
chcp 65001
pnpm install
================================================
FILE: _script/3、buildAndRun.bat
================================================
node -v
cd ../packages/gui
chcp 65001
npm run electron
================================================
FILE: _script/4.1、runTestCore.bat
================================================
node -v
cd ../packages/core
pnpm run test
================================================
FILE: _script/4.2、runTestMitmproxy.bat
================================================
node -v
cd ../packages/mitmproxy
pnpm run test
================================================
FILE: _script/5、generateSetupFile.bat
================================================
node -v
cd ../packages/gui
if not exist "dist_electron" mkdir "dist_electron"
start dist_electron
npm run electron:build
================================================
FILE: doc/caroot.md
================================================
# 关于信任根证书的说明
## 一、为什么要信任根证书。
要回答这个问题需要先掌握下面两个知识点
### 知识点1:什么是根证书
[百度百科-什么是根证书](https://baike.baidu.com/item/%E6%A0%B9%E8%AF%81%E4%B9%A6/9874620?fr=aladdin)
当访问目标网站是https协议时,服务器会发送一个由根证书签发的网站ssl证书给浏览器,让浏览器用这个ssl证书给数据加密。
浏览器需要先验证这个证书的真伪,之后才会使用证书加密。
证书的真伪是通过验证证书的签发机构的证书是否可信,一直追溯到最初始的签发机构的证书(根证书)。
浏览器只需信任根证书,间接的就信任了这条证书链下签发的所有证书。
windows、mac、linux或者浏览器他们都内置了市面上可信的大型证书颁发机构的根证书。
### 知识点2:中间人攻击
本应用的实现原理如下图:

> 简单来说就是DevSidecar在本地启动了一个代理服务器帮你访问目标网站。
> 实际上就是 [中间人攻击](https://baike.baidu.com/item/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB/1739730?fr=aladdin) 的原理,只是本应用没有用它来干坏事,而是帮助开发者加速目标网站的访问。
### 现在可以回答为什么要信任根证书
当目标网站不需要加速拦截时,直接走TCP转发,不需要中间人攻击,没有安全风险,在此不多做讨论。
当目标网站需要拦截时(例如github),就需要通过中间人攻击修改请求或者请求其他替代网站,从而达到加速的目的。
例如加速github就需要修改如下几处
1. 直连访问github需要修改tls握手时的sni域名,规避\*\*\*的sni阻断问题。
2. asserts.github.com等静态资源拦截替换成fastgit.org的镜像地址
DevSidecar在第一次启动时会在本地随机生成一份根证书,当有用户访问github时,就用这份根证书来签发一份假的叫github.com的证书。
如果浏览器事先信任了这份根证书,那么就可以正常访问DevSidecar返回的网页内容了。
## 二、信任根证书有安全风险吗
1. 根证书是DevSidecar第一次启动时本地随机生成的,除了你这台电脑没人知道这份根证书的内容。
2. 代理请求目标网站时会校验目标网站的证书(除非关闭了`代理校验ssl`)。
> 两段链路都是安全的,所以信任根证书没有问题。
> 但如果应用本身来源不明,或者`拦截配置`里的替代网站作恶,则有安全风险。
> 对于应用来源风险:
> 请勿从未知网站下载DevSidecar应用,认准官方版本发布地址
> [Github Release](https://github.com/docmirror/dev-sidecar/releases)
>
> 或者从源码自行编译安装
> 对于拦截配置里的替代网站风险:
>
> 1. 尽量缩小替代配置的范围
> 2. 不使用来源不明的镜像地址,尽量使用知名度较高的镜像地址
> 3. 你甚至可以将其他拦截配置全部删除,只保留github相关配置
================================================
FILE: doc/linux.md
================================================
# Linux 支持
`Linux`使用说明,目前仅官方支持`Ubuntu x86_64 GNOME桌面版(原版)`,其他`Linux`未测试
> 注意:需要开启 [sudo 免密支持](https://www.jianshu.com/p/5d02428f313d),否则请自行安装证书
## 一、安装
### 1.1. Ubuntu / Debian或其衍生版(未测试)
- 下载`DevSidecar-x.x.x.deb`
- 使用 root 执行命令安装 `dpkg -i DevSidecar-x.x.x.deb`
- 去应用列表里面找到 dev-sidecar 应用,打开即可
### 1.2. 其他基于glibc的Linux系统(未测试)
- 下载 `DevSidecar-x.x.x.AppImage`
- 设置可执行权限 `chmod +x DevSidecar-x.x.x.AppImage`
- 双击运行
### 1.3. 特殊的Linux系统(如Alpine和Chimera Linux)
> 此处默认用户有较专业的Linux知识,故不详细描述,请参考并自行试验
- 创建Debian(最方便且省空间)容器,可使用distrobox(推荐),接下来以此为例说明
- 下载deb包并在容器内安装
- 穿透系统设置:
在容器内 `/usr/bin/gsettings` 文件写入:
```bash
#!/bin/sh
distrobox-host-exec gsettings "$@"
```
并设置可执行权限
简化版命令(请在容器内执行):
```
echo -e '#!/bin/sh\ndistrobox-host-exec gsettings "$@"' >/usr/bin/gsettings
```
- 使用命令启动应用,使用“自动安装证书”功能,回到终端,找到输出里含有 `sudo` 的两句命令,复制到主系统执行,如失败(或使用其他证书系统),请自行安装证书,可参考 [议题 #204](https://github.com/docmirror/dev-sidecar/issues/204)
### 1.4. 版本选择
不同CPU架构,选择对应的版本,如果安装失败,请下载 `universal` 版本
## 二、证书安装
默认模式和增强模式需要系统信任CA证书。
由于Linux上火狐和Chrome都不走系统证书,所以除了安装系统证书之外,还需要给浏览器安装证书
### 2.1. 系统证书安装
根据弹出的提示:
- 点击首页右上角“安装根证书”按钮
- 点击“点此去安装”
- 提示安装成功即可
### 2.2. 火狐浏览器安装证书
- 火狐浏览器->选项->隐私与安全->证书->查看证书
- 证书颁发机构->导入
- 选择证书文件在 `~/.dev-sidecar` 目录下
- 勾选信任由此证书颁发机构来标识网站,确定即可
### 2.3. Chrome浏览器安装证书
证书文件目录为 `~/.dev-sidecar`

================================================
FILE: doc/other.md
================================================
# 其他程序使用
## Java程序使用
> 由 [Enaium](https://github.com/Enaium) 提供,未做验证,可供参考
1. 先通过keytool安装证书:
```shell
keytool -import -alias dev-sidecar -keystore "jdk路径\security\cacerts" -file 用户目录\.dev-sidecar\dev-sidecar.ca.crt
```
默认密码为 `changeit`
2. 启动时还需要设置参数,例:
```shell
java -Dhttp.proxyHost=localhost -Dhttp.proxyPort=31181 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=31181 -jar xxxx.jar
```
3. Gradle还需在`用户目录/.gradle/gradle.properties`创建配置文件:
```properties
systemProp.http.proxyHost=localhost
systemProp.http.proxyPort=31181
systemProp.https.proxyHost=localhost
systemProp.https.proxyPort=31181
```
================================================
FILE: doc/recover.md
================================================
# 卸载与恢复网络
由于应用启动后会自动设置系统代理,正常退出时会关闭系统代理。
当应用意外关闭,或者未正常退出后被卸载,此时会因为系统代理没有恢复从而导致完全上不了网。
目前electron在windows系统上无法监听系统重启事件。更多相关资料 [electron issues](https://github.com/electron/electron/pull/24261)
## 恢复代理设置
### 1、windows 代理关闭
如何打开查看windows代理设置:
- win10: 开始->设置->网络和Internet->最下方代理
- win7: 开始->控制面板->网络和Internet->网络和共享中心->左下角Internet选项->连接选项卡->局域网设置

### 2、mac 代理关闭
网络->网卡->代理->去掉http和https的两个勾

### 3、Linux(Ubuntu)
网络->代理->选择禁用
================================================
FILE: doc/wiki/Home.md
================================================
> **给作者打个广告:**
> [https://github.com/certd/certd](https://github.com/certd/certd) 我的开源证书管理工具项目,全自动申请和部署证书,有需求的可以去试试,帮忙点个star
> 注:Wiki还在完善中,敬请期待更多内容。
> 说明:以下文档均以最新版本进行编写,请下载最新版DS后,再参考以下文档使用!
# 一、下载安装:
访问 https://github.com/docmirror/dev-sidecar/releases 页面,下载对应操作系统的安装程序进行安装。
如安装有问题,请查看 [各平台安装说明](https://github.com/docmirror/dev-sidecar/wiki/%E5%90%84%E5%B9%B3%E5%8F%B0%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E)
# 二、功能使用说明:
1. [`加速服务`使用说明](https://github.com/docmirror/dev-sidecar/wiki/%E5%8A%A0%E9%80%9F%E6%9C%8D%E5%8A%A1%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
2. 系统代理使用说明:
3. 通用功能使用说明:
1. 开机自启动:
2. 远程配置:
3. 主题设置:
4. 窗口设置:
5. 检查更新:
4. 应用使用说明:
1. NPM加速:
2. Git.exe加速:
3. PIP加速:
4. 彩蛋(功能增强):
5. 帮助中心
6. 反馈问题
# 三、解决问题:
1. [解决Github访问不了或速度很慢的问题](https://github.com/docmirror/dev-sidecar/wiki/%E8%A7%A3%E5%86%B3Github%E8%AE%BF%E9%97%AE%E4%B8%8D%E4%BA%86%E6%88%96%E9%80%9F%E5%BA%A6%E5%BE%88%E6%85%A2%E7%9A%84%E9%97%AE%E9%A2%98)
2. [Linux安装证书失败的避坑](https://github.com/docmirror/dev-sidecar/issues/238)
3. [解决Linux(deb)系统下无法安装根证书的问题](https://github.com/docmirror/dev-sidecar/issues/135)
4. [在Arch/Fedora下的证书安装](https://github.com/docmirror/dev-sidecar/issues/204)
5. [Mac安装:`无法打开“dev-sidecar”,因为无法验证开发者。` 的解决方案](https://github.com/docmirror/dev-sidecar/issues/147)
6. [在 WSL 中的使用方法](https://github.com/docmirror/dev-sidecar/issues/73)
[> 点击前往Issue区查找更多帮助信息](https://github.com/docmirror/dev-sidecar/issues)
# 四、DevSidecar技术交流群
- QQ 1群:[390691483](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=hIG_VClE1CU2gHuLSSTaazMlo6M760iL&authKey=5VUMMwzH5FeabLDbZNZJbqmZk1gfmB%2B%2FlotO%2Brszz%2BW3E8xwKD2hTg2%2FV2LJEKL7&noverify=0&group_code=390691483),人数:496 / 500
- QQ 2群:[667666069](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=n4nksr4sji93vZtD5e8YEHRT6qbh6VyQ&authKey=XKBZnzmoiJrAFyOT4V%2BCrgX5c13ds59b84g%2FVRhXAIQd%2FlAiilsuwDRGWJct%2B570&noverify=0&group_code=667666069),人数:488 / 500
- QQ 3群:[419807815](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=zRkm0eHUhRmWWJA5O35C7BOKPZ4_gmrz&authKey=X9JHezR1BOalcEmvV6If04TN%2BIbzjAayBDaOSiuOg1SPpPguA7RqoLSHVEeo7A4e&noverify=0&group_code=419807815),人数:494 / 500
- QQ 4群:[438148299](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=i_NCBB5f_Bkm2JsEV1tLs2TkQ79UlCID&authKey=nMsVJbJ6P%2FGNO7Q6vsVUadXRKnULUURwR8zvUZJnP3IgzhHYPhYdcBCHvoOh8vYr&noverify=0&group_code=438148299),人数:295 / 1000
- QQ 5群:[767622917](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=nAWi_Rxj7mM4Unp5LMiatmUWhGimtbcB&authKey=aswmlWGjbt3GIWXtvjB2GJqqAKuv7hWjk6UBs3MTb%2Biyvr%2Fsbb1kA9CjF6sK7Hgg&noverify=0&group_code=767622917),人数:068 / 200(new)
# 五、版本更新日志
https://github.com/docmirror/dev-sidecar/releases
================================================
FILE: doc/wiki/加速服务使用说明.md
================================================
# 1. 加速服务:
1. 什么是 `加速服务`?
- `加速服务` 即 `代理服务`,它通过中间人攻击的方式,将网络请求拦截下来,并经过DNS加速、修改、重定向、代理等一系列的功能,达到加速访问、或访问原本无法访问的站点等目的。
2. 如何启动加速服务:
- 点击首页的【代理服务】右侧的开关按钮,即可启动/关闭加速服务。
- 点击首页的【系统代理】右侧的开关按钮,即可将dev-sidecar设置/不设置为系统默认代理。(系统只能有一个默认代理,在将dev-sidecar与其他网络辅助软件共用时请谨慎开启本开关)
- 点击首页的【NPM加速】和【Git.exe代理】右侧的开关按钮,即可启动/关闭dev-sidecar为对应软件提供的加速服务。如果你的电脑上并未安装NPM或Git,则这两个按钮将不可用,这是正常情况。
# 2. 根证书使用说明:
1. 什么是根证书:TODO
2. [为什么需要安装根证书这么高风险性的步骤](https://github.com/docmirror/dev-sidecar/blob/master/doc/caroot.md)
3. 如何安装根证书:参见dev-sidecar【首页】的【安装根证书】按钮(注意Firefox浏览器还需要一次手动导入根证书)
# 3. 模式:
1. 安全模式:TODO
2. 默认模式:TODO
3. 增强模式(彩蛋):TODO
# 4. 拦截功能使用和配置说明:
## 4.1. 拦截器类型:
### 1)请求拦截器:
| 请求拦截器名称 | 拦截器配置名 | 请求拦截优先级 | 作用 |
| ----------------- | -------------- | ------------- | --------- |
| OPTIONS请求拦截器 | options | 101 | 直接响应200,不发送该OPTIONS请求 |
| 快速成功拦截器 | success | 102 | 直接响应200,不发送该请求 |
| 快速失败拦截器 | abort | 103 | 直接响应403,不发送该请求 |
| 缓存请求拦截器 | cacheXxx | 104 | 如果缓存还生效,直接响应304,不发送该请求
如果缓存已过期或无缓存,则发送请求
注:只对GET请求生效! |
| 重定向拦截器 | redirect | 105 | 重定向到指定地址,直接响应302,不发送该请求 |
| 请求篡改拦截器 | requestReplace | 111 | 篡改请求头,达到想要的目的 |
| 代理拦截器 | proxy | 121 | 将请求转发到指定地址 |
| SNI拦截器 | sni | 122 | 设置 `servername`,用于避开GFW |
### 2)响应拦截器:
| 响应拦截器名称 | 拦截器配置名 | 响应拦截优先级 | 作用 |
| ---------------- | --------------- | ------------- | --------- |
| OPTIONS响应拦截器 | options | 201 | 设置跨域所需的响应头,避免被浏览器的跨域策略阻拦 |
| 缓存响应拦截器 | cacheXxx | 202 | 设置缓存所需的响应头,使浏览器缓存当前请求
注:只对GET请求生效! |
| 响应篡改拦截器 | responseReplace | 203 | 篡改响应头,避免被浏览器的安全策略阻拦 |
| 脚本拦截器 | script | 211 | 注入JavaScript脚本到页面中,如:Github油猴脚本 |
## 4.2. 拦截配置说明书:
TODO:内容待完善
# 5. 域名白名单:
选择哪些域名不会被dev-sidecar处理。
**注意:** 该设置与【系统代理-自定义排除域名】的区别在于:
1. 前者只是被dev-sidecar自身忽略,后者则是写入系统设置、不会被(任何的)系统代理处理,在手动修改系统代理设置时务必小心后者可能残留的作用!
2. 在条目较多时,前者的性能不如后者,可能产生明显延迟。
在config.json的 `proxy.excludeIpList:object` 中设置,**该字段**格式如下:
> 注意:这里点号用来作为JSON object嵌套关系的缩写,冒号指明该条目的类型(主要用来区分object和list),并没有哪一个Object的key为 `proxy.excludeIpList`。为避免歧义,配置中object和list的key总不应包含点号。下同)
```json
{
"proxy": {
"excludeIpList": {
"example1.com": true,
"example2.com": false,
"example3.com": null,
"example4.com": {
"desc1": "域名对应字段设置为false时会被处理,null会移除现有设置(多用于远程配置)",
"desc2": "其他情况下就和设置true一样,不会被处理。因而你可以像这样插入注释",
"desc3": "同样的技巧可以用在其他本应设置一个bool值的地方",
"desc4": "原则上来说config.json不支持//形式的注释,但下文为了方便阅读,还是这么写了"
}
}
}
}
```
# 6. DNS服务管理:
用来配置在dev-sidecar中需要的指定DNS,出于保密和可靠起见建议使用DoH和DoT。
在 `server.dns.provider:object` 中设置,**其中的每个条目** 格式如下:
## 6.1. 配置 `DNS-over-HTTPS`(简称DoH):
> 注:并非被所有DNS支持,但是保证只要能使用就一定匿名且可靠的DNS服务。
```json
"cloudflare": {
"type": "https", // 如果server上以"https://"开头指明了协议,就不需要写type了
"server": "https://1.1.1.1/dns-query",
"cacheSize": 1000
}
```
## 6.2. 配置 `DNS-over-TLS`(简称DoT):
> 并非被所有DNS支持,但是保证只要能使用就一定匿名且可靠的DNS服务。
```json
"cloudflareTLS": {
"type": "tls", // 如果server上以"tls://"开头指明了协议,就不需要写type了
"server": "1.1.1.1",
"port": 853, // 不配置时,默认端口为:853
"servername": "cloudflare-dns.com", // 需要伪造成的SNI
//"sni": "cloudflare-dns.com", // SNI缩写配置
"cacheSize": 1000
}
```
## 6.3. 配置 `TCP` 的DNS服务:
> 并非被所有DNS支持,该方法既不保密也不可靠
```json
"googleTCP": {
"type": "tcp", // 如果server上以"tcp://"开头指明了协议,就不需要写type了
"server": "8.8.8.8",
"port": 53, // 不配置时,默认端口为:53
"cacheSize": 1000
}
```
## 6.4. 配置 `UDP` 的DNS服务:
> 所有DNS服务器均支持UDP方式,但该方法既不保密也不可靠
```json
"google": {
"type": "udp", // 如果server上以"udp://"开头指明了协议,就不需要写type了
"server": "8.8.8.8",
"port": 53, // 不配置时,默认端口为:53
"cacheSize": 1000
}
```
# 7. DNS设置:
选择哪些域名需要使用指定的DNS(需要先在【DNS服务管理】中设置)获取IP。
在config.json中的 `server.dns.mapping:key-value` 中设置,**其中的每个条目**格式如下:
```json
"*.example.com": "your-dns-name"
```
# 8. IP预设置:
为一些DNS无法获取的域名手动设置ip,起到类似于hosts的作用(仅在dev-sidecar开启时生效)。
在config.json中的 `server.preSetIpList:object` 中设置,**其中的每个条目**格式如下:
```json
{
"example.com": {
"1.1.1.1": true, // 如果有多个IP,可以继续添加
"1.0.0.1": false, // 指定为false时,不使用该IP
"2.2.2.2": {
"desc": "这样可以合法的在配置中插入注释。上面使用的//注释方式在文件中是不允许的"
}
}
}
```
# 9. IP测速:
用来对从指定的DNS与IP预设置中获取到的IP测试TCP延迟,也可以用来测试DoH和DoT服务器的可用性,后者操作如下:先在【DNS服务管理】中配置好需要测试的DNS设置,然后在【IP测速】里添加一个没有设置【IP预设置】的辅助域名,并选择使用需检测的DNS进行解析。
对于DoH/DoT而言,由于答案不能被篡改和窃听,所以辅助域名要么获得真实IP(说明可用)要么没有收到答案(说明不可用)。该方法不适用于常规TCP/UDP的DNS,因为它们没有加密,即使收到答案也可能被篡改而不可用)。
在config.json中的 `server.dns.speedTest:object`中设置,**该条目** 格式如下:
```json
"speedTest": {
"hostnameList": [
"example1.com",
"example2.com"
],
"dnsProviders": [
"your-DNS-name-used-in-test1",
"your-DNS-name-used-in-test2"
]
}
```
================================================
FILE: doc/wiki/各平台安装说明.md
================================================
|平台|安装说明 |
|---|---|
| 【Windows】 | 下载后提示无法验证发行者时,选择保留即可
注意:开着ds重启电脑会导致无法上网,你可以再次打开ds,然后右键小图标退出ds即可。[更多说明](https://github.com/docmirror/dev-sidecar/issues/109)|
| 【Mac】 |安装时提示无法验证开发者时,请先取消
然后去系统偏好设置->安全与隐私->下方已阻止使用DevSidecar
选择仍要打开 |
| 【Ubuntu】 | [安装说明](https://github.com/docmirror/dev-sidecar/blob/master/doc/linux.md)|
|【其他Linux】| |
================================================
FILE: doc/wiki/解决Github访问不了或速度很慢的问题.md
================================================
> 注:请使用 `v2.0.0-RC2` 及以上版本,下载地址:https://github.com/docmirror/dev-sidecar/releases
目前,Github通过预设置的IP来访问的,选取测速排在前的IP。
可是,虽然IP测速延迟很低,但是依然会存在不同地区访问部分预设IP不通或很慢的问题。
如果碰到此问题,可以通过将预设IP设置为 `false` 来禁用访问慢的IP,以此达到切换IP的目的,如下图:
如果访问还慢,再将测速排在第1的IP再禁用掉,以此循环,将访问慢的IP都禁掉,直到选取到的IP访问Github速度很快为止。
> 假如:测速排第1的IP为 `20.27.177.113`,则将其配置为 `false`,或者删除该IP

================================================
FILE: eslint.config.js
================================================
import antfu from '@antfu/eslint-config'
export default antfu(
{
vue: {
vueVersion: 2,
},
rules: {
'style/brace-style': ['error', '1tbs'],
'style/space-before-function-paren': ['error', 'always'],
'import/newline-after-import': 'off',
'import/first': 'off',
'perfectionist/sort-imports': 'off',
'node/prefer-global/buffer': 'off',
'node/prefer-global/process': 'off',
'no-console': 'off',
},
ignores: [
'**/build/*',
'**/dist_electron',
],
formatters: {
css: true,
html: true,
markdown: 'prettier',
},
},
)
================================================
FILE: package.json
================================================
{
"name": "dev-sidecar-parent",
"type": "module",
"private": false,
"packageManager": "pnpm@9.13.2",
"author": "Greper",
"license": "MPL-2.0",
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"devDependencies": {
"@antfu/eslint-config": "^3.9.1",
"eslint": "^9.15.0",
"eslint-plugin-format": "^0.1.2"
},
"pnpm": {
"supportedArchitectures": {
"os": ["current"],
"cpu": ["x64", "arm64", "ia32"]
}
}
}
================================================
FILE: packages/cli/LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: packages/cli/cli.js
================================================
#!/usr/bin/env node
require('./src')
================================================
FILE: packages/cli/package.json
================================================
{
"name": "@docmirror/dev-sidecar-cli",
"version": "2.0.1",
"private": false,
"description": "给开发者的加速代理工具",
"author": "docmirror.cn",
"license": "MPL-2.0",
"keywords": [
"dev-sidecar",
"github加速",
"google加速",
"代理"
],
"main": "src/index.js",
"bin": "./cli.js",
"scripts": {
"start": "node ./src"
},
"dependencies": {
"@docmirror/dev-sidecar": "workspace:*",
"@docmirror/mitmproxy": "workspace:*"
}
}
================================================
FILE: packages/cli/src/banner.txt
================================================
____ _____ _ __
/ __ \___ _ __ / ___/(_)___/ /__ _________ ______
/ / / / _ \ | / /_____\__ \/ / __ / _ \/ ___/ __ `/ ___/
/ /_/ / __/ |/ /_____/__/ / / /_/ / __/ /__/ /_/ / /
/_____/\___/|___/ /____/_/\__,_/\___/\___/\__,_/_/
==================== 开发者边车 ====================
================================================
FILE: packages/cli/src/index.js
================================================
const fs = require('node:fs')
const DevSidecar = require('@docmirror/dev-sidecar')
const jsonApi = require('@docmirror/mitmproxy/src/json')
// 启动服务
const mitmproxyPath = './mitmproxy'
async function startup () {
const banner = fs.readFileSync('./banner.txt')
console.log(banner.toString())
const configPath = './user_config.json5'
if (fs.existsSync(configPath)) {
const file = fs.readFileSync(configPath)
let userConfig
try {
userConfig = jsonApi.parse(file.toString())
console.info(`读取和解析 user_config.json5 成功:${configPath}`)
} catch (e) {
console.error(`读取或解析 user_config.json5 失败: ${configPath}, error:`, e)
userConfig = {}
}
DevSidecar.api.config.set(userConfig)
}
await DevSidecar.api.startup({ mitmproxyPath })
console.log('dev-sidecar 已启动')
}
async function onClose () {
console.log('on sigint ')
await DevSidecar.api.shutdown()
console.log('on closed ')
process.exit(0)
}
process.on('SIGINT', onClose)
startup()
================================================
FILE: packages/cli/src/mitmproxy.js
================================================
const fs = require('node:fs')
const path = require('node:path')
const server = require('@docmirror/mitmproxy')
const jsonApi = require('@docmirror/mitmproxy/src/json')
const log = require('@docmirror/mitmproxy/src/utils/util.log.server') // 当前脚本是在 server 的进程中执行的,所以使用 mitmproxy 中的logger
const home = process.env.USER_HOME || process.env.HOME || 'C:/Users/Administrator/'
let configPath
if (process.argv && process.argv.length > 3) {
configPath = process.argv[2]
} else {
configPath = path.join(home, '.dev-sidecar/running.json')
}
const configJson = fs.readFileSync(configPath)
log.info('读取 running.json by cli 成功:', configPath)
let config
try {
config = jsonApi.parse(configJson.toString())
} catch (e) {
log.error(`running.json 文件内容格式不正确,文件路径:${configPath},文件内容: ${configJson.toString()}, error:`, e)
config = {}
}
// const scriptDir = '../../gui/extra/scripts/'
// config.setting.script.defaultDir = path.join(__dirname, scriptDir)
// const pacFilePath = '../../gui/extra/pac/pac.txt'
// config.plugin.overwall.pac.customPacFilePath = path.join(__dirname, pacFilePath)
config.setting.rootDir = path.join(__dirname, '../../gui/')
log.info(`start mitmproxy by cli, configPath: ${configPath}`)
server.start(config)
================================================
FILE: packages/cli/src/user_config.json5
================================================
{
"app": {
"autoStart": {
"enabled": true
},
"mode": "default"
},
"plugin": {
"node": {
"setting": {
"yarnRegistry": "null"
}
},
"git": {
"enabled": true
},
"overwall": {
"enabled": false,
"targets": {
"*gagedigital.com": true,
"*yonsz.net": true,
"*bootstrapcdn.com": true,
"*cloudflare.com": true,
"help.yonsz.net": true
}
}
},
"server": {
"intercepts": {
"dev-sidecar.docmirror.cn": {
".*": {
"proxy": "dev-sidecar-preview.docmirror.cn"
}
},
"test1111.gagedigital.com": {
".*": {
"proxy": "test1.gagedigital.com"
}
}
}
}
}
================================================
FILE: packages/core/LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: packages/core/index.js
================================================
module.exports = require('./src')
// TODO 这是一个解谜游戏 ↓ ↓ ↓ ↓ ↓ ↓ ,如果你破解了它,请不要公开,好好用它来学习和查资料吧(特别注意:为了你的人身安全,请不要用它来查看和发表不当言论,你懂得)。
/**
\u0061\u0048\u0052\u0030\u0063\u0044\u006f\u0076\u004c\u0032\u0052\u006c\u0064\u0069\u0031\u007a\u0061\u0057\u0052\u006c\u0059\u0032\u0046\u0079\u004c\u006d\u0052\u0076\u0059\u0032\u0031\u0070\u0063\u006e\u004a\u0076\u0063\u0069\u0035\u006a\u0062\u0069\u0039\u0035\u0062\u0033\u0056\u006d\u0061\u0057\u0035\u006b\u0061\u0058\u0051\u0076\u0061\u0057\u0035\u006b\u005a\u0058\u0067\u0075\u0061\u0048\u0052\u0074\u0062\u0041\u003d\u003d
*/
// 这个项目里有一点点解谜提示: https://github.com/fast-crud/fast-crud (打开拉到最下面)
================================================
FILE: packages/core/package.json
================================================
{
"name": "@docmirror/dev-sidecar",
"version": "2.0.1",
"private": false,
"description": "给开发者的加速代理工具",
"author": "docmirror.cn",
"license": "MPL-2.0",
"keywords": [
"dev-sidecar",
"github加速",
"google加速",
"代理"
],
"main": "src/index.js",
"scripts": {
"test": "mocha"
},
"dependencies": {
"@starknt/sysproxy": "^0.0.3",
"@vscode/sudo-prompt": "^9.3.1",
"fix-path": "^3.0.0",
"iconv-lite": "^0.6.3",
"lodash": "^4.17.21",
"log4js": "^6.9.1",
"node-powershell": "^4.0.0",
"spawn-sync": "^2.0.0",
"winreg": "^1.2.5"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^8.2.1"
}
}
================================================
FILE: packages/core/src/config/index.js
================================================
const path = require('node:path')
const configLoader = require('./local-config-loader')
function getRootCaCertPath () {
return path.join(configLoader.getUserBasePath(), '/dev-sidecar.ca.crt')
}
function getRootCaKeyPath () {
return path.join(configLoader.getUserBasePath(), '/dev-sidecar.ca.key.pem')
}
const defaultConfig = {
app: {
mode: 'default',
autoStart: {
enabled: false,
},
remoteConfig: {
enabled: true,
// 共享远程配置地址
url: 'https://gitee.com/wangliang181230/dev-sidecar/raw/docmirror2.x/packages/core/src/config/remote_config.json',
// 个人远程配置地址
personalUrl: '',
},
startShowWindow: true, // 启动时是否打开窗口:true=打开窗口, false=隐藏窗口
needCheckHideWindow: true, // 是否需要在隐藏窗口时做检查
showHideShortcut: 'Alt + S', // 显示/隐藏窗口快捷键
windowSize: { width: 900, height: 750 }, // 启动时,窗口的尺寸
theme: 'dark', // 主题:light=亮色, dark=暗色
autoChecked: true, // 是否自动检查更新
skipPreRelease: true, // 是否忽略预发布版本
dock: {
hideWhenWinClose: false,
},
closeStrategy: 0,
showShutdownTip: true,
// 日志相关配置
logFileSavePath: path.join(configLoader.getUserBasePath(), '/logs'), // 日志文件保存路径
keepLogFileCount: 15, // 保留日志文件数
maxLogFileSize: 1, // 最大日志文件大小
maxLogFileSizeUnit: 'GB', // 最大日志文件大小单位
},
server: {
enabled: true,
host: '127.0.0.1',
port: 31181,
setting: {
NODE_TLS_REJECT_UNAUTHORIZED: true,
verifySsl: true,
script: {
enabled: true,
defaultDir: './extra/scripts/',
},
userBasePath: configLoader.getUserBasePath(),
rootCaFile: {
certPath: getRootCaCertPath(),
keyPath: getRootCaKeyPath(),
},
// 默认超时时间配置
defaultTimeout: 20000, // 请求超时时间
defaultKeepAliveTimeout: 30000, // 连接超时时间
// 指定域名超时时间配置
timeoutMapping: {
'github.com': {
timeout: 20000,
keepAliveTimeout: 30000,
},
},
// 慢速IP延迟时间:测速超过该值时,则视为延迟高,显示为橙色
lowSpeedDelay: 200,
},
compatible: {
// **** 自定义兼容配置 **** //
// connect阶段所需的兼容性配置
connect: {
// 参考配置(无path)
// 'xxx.xxx.xxx.xxx:443': {
// ssl: false
// }
},
// request阶段所需的兼容性配置
request: {
// 参考配置(配置方式同 `拦截配置`)
// 'xxx.xxx.xxx.xxx:443': {
// '.*': {
// rejectUnauthorized: false
// }
// }
},
},
intercept: {
enabled: true,
},
intercepts: {
'github.com': {
'.*': {
sni: 'baidu.com',
},
'^(/[\\w-.]+){2,}/?(\\?.*)?$': {
// 篡改猴插件地址,以下是高速镜像地址
tampermonkeyScript: 'https://gitee.com/wangliang181230/dev-sidecar/raw/scripts/tampermonkey.js',
// Github油猴脚本地址,以下是高速镜像地址
script: 'https://gitee.com/wangliang181230/dev-sidecar/raw/scripts/GithubEnhanced-High-Speed-Download.user.js',
remark: '注:上面所使用的脚本地址,为高速镜像地址。',
desc: '油猴脚本:高速下载 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、项目列表单文件快捷下载、添加 git clone 命令',
},
// 以下三项暂时先注释掉,因为已经有油猴脚本提供高速下载地址了。
// '/.*/.*/releases/download/': {
// redirect: 'gh.api.99988866.xyz/https://github.com',
// desc: 'release文件加速下载跳转地址'
// },
// '/.*/.*/archive/': {
// redirect: 'gh.api.99988866.xyz/https://github.com'
// },
// 以下代理地址不支持该类资源的代理,暂时注释掉
// '/.*/.*/blame/': {
// redirect: 'gh.api.99988866.xyz/https://github.com'
// },
'/fluidicon.png': {
cacheDays: 365,
desc: 'Github那只猫的图片,缓存1年',
},
'^(/[^/]+){2}/pull/\\d+/open_with_menu.*$': {
cacheDays: 7,
desc: 'PR详情页:标题右边那个Code按钮的HTML代码请求地址,感觉上应该可以缓存。暂时先设置为缓存7天',
},
'^((/[^/]+){2,})/raw((/[^/]+)+\\.(jpg|jpeg|png|gif))(\\?.*)?$': {
// eslint-disable-next-line no-template-curly-in-string
proxy: 'https://raw.githubusercontent.com${m[1]}${m[3]}',
sni: 'baidu.com',
cacheDays: 7,
desc: '仓库内图片,重定向改为代理,并缓存7天。',
},
'^((/[^/]+){2,})/raw((/[^/]+)+\\.js)(\\?.*)?$': {
// eslint-disable-next-line no-template-curly-in-string
proxy: 'https://raw.githubusercontent.com${m[1]}${m[3]}',
sni: 'baidu.com',
responseReplace: { headers: { 'content-type': 'application/javascript; charset=utf-8' } },
desc: '仓库内脚本,重定向改为代理,并设置响应头Content-Type。作用:方便script拦截器直接使用,避免引起跨域问题和脚本内容限制问题。',
},
},
'github.githubassets.com': {
'.*': {
sni: 'baidu.com',
},
},
'camo.githubusercontent.com': {
'^[a-zA-Z0-9/]+(\\?.*)?$': {
cacheDays: 365,
desc: '图片,缓存1年',
},
},
'collector.github.com': {
'.*': {
sni: 'baidu.com',
},
},
'customer-stories-feed.github.com': {
'.*': { proxy: 'customer-stories-feed.fastgit.org' },
},
'user-images.githubusercontent.com': {
'^/.*\\.png(\\?.*)?$': {
cacheDays: 365,
desc: '用户在PR或issue等内容中上传的图片,缓存1年。注:每张图片都有唯一的ID,不会重复,可以安心缓存',
},
},
'private-user-images.githubusercontent.com': {
'^/.*\\.png(\\?.*)?$': {
cacheDays: 30,
cacheHours: null,
desc: '用户在PR或issue等内容中上传的图片,缓存30天',
},
},
'avatars.githubusercontent.com': {
'^/u/\\d+(\\?.*)?$': {
cacheDays: 365,
desc: '用户头像,缓存1年',
},
},
'api.github.com': {
'^/_private/browser/stats$': {
success: true,
desc: 'github的访问速度分析上传,没有必要,直接返回成功',
},
'.*': {
sni: 'baidu.com',
},
},
'*.docker.com': {
'.*': {
sni: 'baidu.com',
},
},
'login.docker.com': {
'/favicon.ico': {
proxy: 'hub.docker.com',
sni: 'baidu.com',
desc: '登录页面的ico,采用hub.docker.com的',
},
},
// google cdn
'www.google.com': {
'/recaptcha/.*': { proxy: 'www.recaptcha.net' },
// '.*': {
// proxy: 'gg.docmirror.top/_yxorp',
// desc: '呀,被你发现了,偷偷的用,别声张'
// }
},
'www.gstatic.com': {
'/recaptcha/.*': { proxy: 'www.recaptcha.net' },
},
'ajax.googleapis.com': {
'.*': {
proxy: 'ajax.lug.ustc.edu.cn',
backup: ['gapis.geekzu.org'],
test: 'ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js',
},
},
'fonts.googleapis.com': {
'.*': {
proxy: 'fonts.loli.net',
test: 'https://fonts.googleapis.com/css?family=Oswald',
},
},
'themes.googleapis.com': {
'.*': {
proxy: 'themes.loli.net',
backup: ['themes.proxy.ustclug.org'],
},
},
'themes.googleusercontent.com': {
'.*': { proxy: 'google-themes.proxy.ustclug.org' },
},
// 'fonts.gstatic.com': {
// '.*': {
// proxy: 'gstatic.loli.net',
// backup: ['fonts-gstatic.proxy.ustclug.org']
// }
// },
'clients*.google.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
'www.googleapis.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
'lh*.googleusercontent.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
// mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.0/napi-v3-win32-x64.tar.gz
'*.s3.1amazonaws1.com': {
'/sqlite3/.*': {
redirect: 'npm.taobao.org/mirrors',
},
},
// 'packages.elastic.co': { '.*': { proxy: 'elastic.proxy.ustclug.org' } },
// 'ppa.launchpad.net': { '.*': { proxy: 'launchpad.proxy.ustclug.org' } },
// 'archive.cloudera.com': { '.*': { regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' } },
// 'downloads.lede-project.org': { '.*': { proxy: 'lede.proxy.ustclug.org' } },
// 'downloads.openwrt.org': { '.*': { proxy: 'openwrt.proxy.ustclug.org' } },
// 'secure.gravatar.com': { '.*': { proxy: 'gravatar.proxy.ustclug.org' } },
'*.carbonads.com': {
'/carbon.*': {
abort: true,
desc: '广告拦截',
},
},
'*.buysellads.com': {
'/ads/.*': {
abort: true,
desc: '广告拦截',
},
},
},
// 预设置IP列表
preSetIpList: {
'github.com': {
'4.237.22.38': true,
'20.26.156.215': true,
'20.27.177.113': true,
'20.87.245.0': true,
'20.200.245.247': true,
'20.201.28.151': true,
'20.205.243.166': true,
'140.82.113.3': true,
'140.82.114.4': true,
'140.82.116.3': true,
'140.82.116.4': true,
'140.82.121.3': true,
'140.82.121.4': true,
},
'api.github.com': {
'20.26.156.210': true,
'20.27.177.116': true,
'20.87.245.6': true,
'20.200.245.245': true,
'20.201.28.148': true,
'20.205.243.168': true,
'20.248.137.49': true,
'140.82.112.5': true,
'140.82.113.6': true,
'140.82.116.6': true,
'140.82.121.6': true,
},
'codeload.github.com': {
'20.26.156.216': true,
'20.27.177.114': true,
'20.87.245.7': true,
'20.200.245.246': true,
'20.201.28.149': true,
'20.205.243.165': true,
'20.248.137.55': true,
'140.82.113.9': true,
'140.82.114.10': true,
'140.82.116.10': true,
'140.82.121.9': true,
},
'*.githubusercontent.com': {
'146.75.92.133': true,
'199.232.88.133': true,
'199.232.144.133': true,
},
'viewscreen.githubusercontent.com': {
'140.82.112.21': true,
'140.82.112.22': true,
'140.82.113.21': true,
'140.82.113.22': true,
'140.82.114.21': true,
'140.82.114.22': true,
},
'github.io': {
'185.199.108.153': true,
'185.199.109.153': true,
'185.199.110.153': true,
'185.199.111.153': true,
},
'*.githubassets.com': {
'185.199.108.154': true,
'185.199.109.154': true,
'185.199.110.154': true,
'185.199.111.154': true,
},
'^(analytics|ghcc)\\.githubassets\\.com$': {
'185.199.108.153': true,
'185.199.110.153': true,
'185.199.109.153': true,
'185.199.111.153': true,
},
'*.pixiv.net': {
// 以下为 `cdn-origin.pixiv.net` 域名的IP
'210.140.139.154': true,
'210.140.139.157': true,
'210.140.139.160': true,
},
'hub.docker.com': {
'44.221.37.199': true,
'52.44.227.212': true,
'54.156.140.159': true,
},
'sessions-bugsnag.docker.com': {
'44.221.37.199': true,
'52.44.227.212': true,
'54.156.140.159': true,
},
},
whiteList: {
'*.cn': true,
'cn.*': true,
'*china*': true,
'*.dingtalk.com': true,
'*.apple.com': true,
'*.microsoft.com': true,
'*.alipay.com': true,
'*.qq.com': true,
'*.baidu.com': true,
'192.168.*': true,
},
dns: {
providers: {
aliyun: {
type: 'https',
server: 'https://dns.alidns.com/dns-query',
cacheSize: 1000,
},
cloudflare: {
type: 'https',
server: 'https://1.1.1.1/dns-query',
cacheSize: 1000,
},
quad9: {
type: 'https',
server: 'https://9.9.9.9/dns-query',
cacheSize: 1000,
},
safe360: {
type: 'https',
server: 'https://doh.360.cn/dns-query',
cacheSize: 1000,
forSNI: true,
},
rubyfish: {
type: 'https',
server: 'https://rubyfish.cn/dns-query',
cacheSize: 1000,
},
},
mapping: {
'*.github.com': 'quad9',
'*github*.com': 'quad9',
'*.github.io': 'quad9',
'*.docker.com': 'quad9',
'*.stackoverflow.com': 'quad9',
'*.electronjs.org': 'quad9',
'*.amazonaws.com': 'quad9',
'*.yarnpkg.com': 'quad9',
'*.cloudfront.net': 'quad9',
'*.cloudflare.com': 'quad9',
'img.shields.io': 'quad9',
'*.vuepress.vuejs.org': 'quad9',
'*.gh.docmirror.top': 'quad9',
'*.v2ex.com': 'quad9',
'*.pypi.org': 'quad9',
'*.jetbrains.com': 'quad9',
'*.azureedge.net': 'quad9',
},
speedTest: {
enabled: true,
interval: 300000,
hostnameList: ['github.com'],
dnsProviders: ['cloudflare', 'safe360', 'rubyfish'],
},
},
},
proxy: {},
plugin: {},
help: {
dataList: [
{
title: '查看DevSidecar的说明文档(Wiki)',
url: 'https://github.com/docmirror/dev-sidecar/wiki',
},
{
title: '为了展示更多帮助信息,请启用 “远程配置” 功能!!!',
},
],
},
}
// 从本地文件中加载配置
defaultConfig.configFromFiles = configLoader.getConfigFromFiles(configLoader.getUserConfig(), defaultConfig)
module.exports = defaultConfig
================================================
FILE: packages/core/src/config/local-config-loader.js
================================================
const fs = require('node:fs')
const path = require('node:path')
const lodash = require('lodash')
const jsonApi = require('@docmirror/mitmproxy/src/json')
const mergeApi = require('../merge')
const logOrConsole = require('../utils/util.log-or-console')
function getUserBasePath (autoCreate = true) {
const userHome = process.env.USERPROFILE || process.env.HOME || '/'
const dir = path.resolve(userHome, './.dev-sidecar')
// 自动创建目录
if (autoCreate && !fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
return dir
}
function loadConfigFromFile (configFilePath) {
if (configFilePath == null) {
logOrConsole.error('配置文件地址为空')
return {}
}
if (!fs.existsSync(configFilePath)) {
logOrConsole.info('配置文件不存在:', configFilePath)
return {} // 文件不存在,返回空配置
}
// 读取配置文件
let configStr
try {
configStr = fs.readFileSync(configFilePath)
} catch (e) {
logOrConsole.error('读取配置文件失败:', configFilePath, ', error:', e)
return {}
}
// 解析配置文件
try {
const config = jsonApi.parse(configStr)
logOrConsole.info('读取配置文件成功:', configFilePath)
return config
} catch (e) {
logOrConsole.error(`解析配置文件失败,文件内容格式不正确,文件路径: ${configFilePath},文件内容:${configStr},error:`, e)
return {}
}
}
function getUserConfigPath () {
const dir = getUserBasePath()
// 兼容1.7.3及以下版本的配置文件处理逻辑
const newFilePath = path.join(dir, '/config.json')
const oldFilePath = path.join(dir, '/config.json5')
if (!fs.existsSync(newFilePath) && fs.existsSync(oldFilePath)) {
return oldFilePath // 如果新文件不存在,但旧文件存在,则返回旧文件路径
}
return newFilePath
}
function getUserConfig () {
const configFilePath = getUserConfigPath()
return loadConfigFromFile(configFilePath)
}
function getRemoteConfigPath (suffix = '') {
const dir = getUserBasePath()
return path.join(dir, `/remote_config${suffix}.json5`)
}
function getRemoteConfig (suffix = '') {
const remoteConfigFilePath = getRemoteConfigPath(suffix)
return loadConfigFromFile(remoteConfigFilePath)
}
function getAutomaticCompatibleConfigPath () {
const dir = getUserBasePath()
return path.join(dir, '/automaticCompatibleConfig.json')
}
/**
* 从文件读取配置
*
* @param userConfig 用户配置
* @param defaultConfig 默认配置
*/
function getConfigFromFiles (userConfig, defaultConfig) {
const merged = userConfig != null ? lodash.cloneDeep(userConfig) : {}
const personalRemoteConfig = getRemoteConfig('_personal')
const shareRemoteConfig = getRemoteConfig()
mergeApi.doMerge(merged, personalRemoteConfig) // 先合并一次个人远程配置,使配置顺序在前
mergeApi.doMerge(merged, shareRemoteConfig) // 先合并一次共享远程配置,使配置顺序在前
mergeApi.doMerge(merged, defaultConfig) // 合并默认配置,顺序排在最后
mergeApi.doMerge(merged, shareRemoteConfig) // 再合并一次共享远程配置,使配置生效
mergeApi.doMerge(merged, personalRemoteConfig) // 再合并一次个人远程配置,使配置生效
if (userConfig != null) {
mergeApi.doMerge(merged, userConfig) // 再合并一次用户配置,使用户配置重新生效
}
// 删除为null及[delete]的项
mergeApi.deleteNullItems(merged)
logOrConsole.info('加载及合并远程配置完成')
return merged
}
module.exports = {
getUserBasePath,
loadConfigFromFile,
getUserConfigPath,
getUserConfig,
getRemoteConfigPath,
getRemoteConfig,
getAutomaticCompatibleConfigPath,
getConfigFromFiles,
}
================================================
FILE: packages/core/src/config/remote_config.json5
================================================
{
"server": {
"compatible": {
"connect": {
"218.18.106.132:443": {
"ssl": true
}
},
"request": {
"218.18.106.132:443": {
"rejectUnauthorized": false
}
}
},
"intercepts": {
"github.com": {
"^(/[\\w-.]+){2,}/?(\\?.*)?$": {
"tampermonkeyScript": "https://gitee.com/wangliang181230/dev-sidecar/raw/scripts/tampermonkey.js",
"script": "https://gitee.com/wangliang181230/dev-sidecar/raw/scripts/GithubEnhanced-High-Speed-Download.user.js"
},
"^(/[^/]+){2}/releases/download/.*$": {
"redirect": "ghp.ci/https://github.com",
"desc": "release文件加速下载重定向地址"
},
"^(/[^/]+){2}/archive/.*\\.(zip|tar.gz)$": {
"redirect": "ghp.ci/https://github.com",
"desc": "release源代码加速下载重定向地址"
},
"^((/[^/]+){2,})/raw((/[^/]+)+\\.(jpg|jpeg|png|gif))(\\?.*)?$": {
"sni": "baidu.com" // proxy拦截器不会使用 .* 中的sni配置,故补充此配置
},
"^((/[^/]+){2,})/raw((/[^/]+)+\\.js)(\\?.*)?$": {
"sni": "baidu.com" // proxy拦截器不会使用 .* 中的sni配置,故补充此配置
}
},
"api.github.com": {
".*": {
"sni": "baidu.com"
}
},
"github.githubassets.com": {
".*": {
"sni": "baidu.com"
}
},
"avatars.githubusercontent.com": {
".*": {
"sni": "baidu.com"
}
},
"camo.githubusercontent.com": {
".*": {
"sni": "baidu.com"
}
},
"collector.github.com": {
".*": {
"sni": "baidu.com"
}
},
"www.gstatic.com": {
"/recaptcha/.*": {
"proxy": "www.recaptcha.net"
}
}
},
"preSetIpList": {
"github.com": [
"4.237.22.38",
"20.26.156.215",
"20.27.177.113",
"20.87.245.0",
"20.200.245.247",
"20.201.28.151",
"20.205.243.166",
"140.82.113.3",
"140.82.114.4",
"140.82.116.3",
"140.82.116.4",
"140.82.121.3",
"140.82.121.4"
],
"hub.docker.com": null // 1.8.2版本中,该域名的预设IP有问题,现在远程配置中删除
},
"dns": {
"mapping": {
"*.jetbrains.com": "quad9",
"*.azureedge.net": "quad9",
"*.stackoverflow.com": "quad9"
},
"speedTest": {
"interval": 300000
}
},
"whiteList": {
"*.icloud.com": true,
"*.lenovo.net": true
}
},
"proxy": {
"remoteDomesticDomainAllowListFileUrl": "https://raw.kkgithub.com/pluwen/china-domain-allowlist/main/allow-list.sorl",
"excludeIpList": {
// Github文件上传所使用的域名,被DS代理会导致文件上传经常失败,从系统代理中排除掉
"objects-origin.githubusercontent.com": true,
// Github通过Actions上传的文件,下载时所需的域名,从系统代理中排除掉,否则下载会失败
"*.windows.net": true,
// Github下载release文件的高速镜像地址
"*.ghproxy.net": true,
"*.ghp.ci": true,
"*.kkgithub.com": true,
// Github建站域名
"*.github.io": true,
// bilibili相关
"*.bilicomic.com": true,
// 中国移动云盘登录API
"[2049:8c54:813:10c::140]": true,
"[2409:8a0c:a442:ff40:a51f:4b9c:8b41:25ea]": true,
"[2606:2800:147:120f:30c:1ba0:fc6:265a]": true,
// 移动云盘相关
"*.cmicapm.com": true,
// cloudflare:排除以下域名,cloudflare的人机校验会更快,成功率更高。
"*.cloudflare.com": true,
"*.cloudflare-cn.com": true,
// VS相关
"*.microsoftonline.com": true, // 此域名不排除的话,部分功能将出现异常
"*.msecnd.net": true,
"*.msedge.net": true,
// 卡巴斯基升级域名
"*kaspersky*.com": true,
"*.upd.kaspersky.com": true,
// sandbox沙盒域名
"*.sandboxie-plus.com": true,
// 无忧论坛
"*.wuyou.net": true,
// python建图包域名(浏览器)
"*.pyecharts.org": true,
// 教育网站
"*.bcloudlink.com": true,
// 奇迹秀(资源)
"*.qijishow.com": true,
// Z-Library
"*.z-lib.fo": true,
// Finalshell(Linux学习网)
"*.finalshell.com": true,
// MineBBS(我的世界中文论坛)
"*.minebbs.com": true,
// 我的世界插件网
"*.spigotmc.org": true,
// bd测试
"*.virustotal.com": true,
// 未知
"*.youdemai.com": true,
"*.casualthink.com": true,
"44.239.165.12": true,
"3.164.110.117": true
}
},
"plugin": {
"overwall": {
"targets": {
"*.github.com": true,
"*github*.com": true,
"*.nodejs.org": true,
"*.npmjs.com": true,
"*.wikimedia.org": true,
"*.v2ex.com": true,
"*.azureedge.net": true,
"*.cloudfront.net": true,
"*.bing.com": true,
"*.discourse-cdn.com": true,
"*.gravatar.com": true,
"*.docker.com": true,
"*.vueuse.org": true,
"*.elastic.co": true,
"*.optimizely.com": true,
"*.stackpathcdn.com": true,
"*.fastly.net": true,
"*.cloudflare.com": true,
"*.233v2.com": true,
"*.v2fly.org": true,
"*.telegram.org": true,
"*.amazon.com": true,
"*.googleapis.com": true,
"*.google-analytics.com": true,
"*.cloudflareinsights.com": true,
"*.intlify.dev": true,
"*.segment.io": true,
"*.shields.io": true,
"*.jsdelivr.net": true,
"*.z-library.sk": true,
"*.zlibrary*.se": true,
// 维基百科
"*.wikipedia-on-ipfs.org": true,
// ChatGPT
"*.oaiusercontent.com": true, // 在ChatGPT中生成文件并下载所需的域名
// Pixiv相关
"*.pixiv.org": true,
"*.fanbox.cc": true,
"*.onesignal.com": true // pixiv站点,会加载该域名下的js脚本
},
"pac": {
"pacFileUpdateUrl": "https://raw.kkgithub.com/gfwlist/gfwlist/master/gfwlist.txt"
}
}
}
}
================================================
FILE: packages/core/src/config-api.js
================================================
const fs = require('node:fs')
const jsonApi = require('@docmirror/mitmproxy/src/json')
const lodash = require('lodash')
const request = require('request')
const defConfig = require('./config/index.js')
const mergeApi = require('./merge.js')
const Shell = require('./shell')
const log = require('./utils/util.log.core')
const configLoader = require('./config/local-config-loader')
let configTarget = lodash.cloneDeep(defConfig)
function get () {
return configTarget
}
let timer
const configApi = {
async startAutoDownloadRemoteConfig () {
if (timer != null) {
clearInterval(timer)
}
const download = async () => {
try {
await configApi.downloadRemoteConfig()
configApi.reload()
} catch (e) {
log.error('定时下载远程配置并重载配置失败', e)
}
}
await download()
timer = setInterval(download, 24 * 60 * 60 * 1000) // 1天
},
async downloadRemoteConfig () {
if (get().app.remoteConfig.enabled !== true) {
// 删除保存的远程配置文件
configApi.deleteRemoteConfigFile()
configApi.deleteRemoteConfigFile('_personal')
return
}
const remoteConfig = get().app.remoteConfig
await configApi.doDownloadRemoteConfig(remoteConfig.url)
await configApi.doDownloadRemoteConfig(remoteConfig.personalUrl, '_personal')
},
doDownloadRemoteConfig (remoteConfigUrl, suffix = '') {
if (!remoteConfigUrl) {
// 删除保存的远程配置文件
configApi.deleteRemoteConfigFile(suffix)
return
}
return new Promise((resolve, reject) => {
log.info('开始下载远程配置:', remoteConfigUrl)
const headers = {
'Cache-Control': 'no-cache', // 禁止使用缓存
'Pragma': 'no-cache', // 禁止使用缓存
}
if (remoteConfigUrl.startsWith('https://raw.githubusercontent.com/')) {
headers['Server-Name'] = 'baidu.com'
}
request(remoteConfigUrl, { headers }, (error, response, body) => {
if (error) {
log.error(`下载远程配置失败: ${remoteConfigUrl}, error:`, error, ', response:', response, ', body:', body)
reject(error)
return
}
if (response && response.statusCode === 200) {
if (body == null || body.length < 2) {
log.warn('下载远程配置成功,但内容为空:', remoteConfigUrl)
resolve()
return
} else {
log.info('下载远程配置成功:', remoteConfigUrl)
}
// 尝试解析远程配置,如果解析失败,则不保存它
let remoteConfig
try {
remoteConfig = jsonApi.parse(body)
} catch {
log.error(`远程配置内容格式不正确, url: ${remoteConfigUrl}, body: ${body}`)
remoteConfig = null
}
if (remoteConfig != null) {
const remoteSavePath = configLoader.getRemoteConfigPath(suffix)
try {
fs.writeFileSync(remoteSavePath, body)
log.info('保存远程配置文件成功:', remoteSavePath)
} catch (e) {
log.error('保存远程配置文件失败:', remoteSavePath, ', error:', e)
reject(new Error(`保存远程配置文件失败: ${e.message}`))
return
}
} else {
log.warn('远程配置对象为空:', remoteConfigUrl)
}
resolve()
} else {
log.error(`下载远程配置失败: ${remoteConfigUrl}, response:`, response, ', body:', body)
let message
if (response) {
message = `下载远程配置失败: ${remoteConfigUrl}, message: ${response.message}, code: ${response.statusCode}`
} else {
message = `下载远程配置失败: response: ${response}`
}
reject(new Error(message))
}
})
})
},
deleteRemoteConfigFile (suffix = '') {
const remoteSavePath = configLoader.getRemoteConfigPath(suffix)
if (fs.existsSync(remoteSavePath)) {
fs.unlinkSync(remoteSavePath)
log.info('删除远程配置文件成功:', remoteSavePath)
}
},
readRemoteConfigStr (suffix = '') {
try {
const path = configLoader.getRemoteConfigPath(suffix)
if (fs.existsSync(path)) {
const file = fs.readFileSync(path)
log.info('读取远程配置文件内容成功:', path)
return file.toString()
} else {
log.info('远程配置文件不存在:', path)
}
} catch (e) {
log.error('读取远程配置文件内容失败:', e)
}
return '{}'
},
/**
* 保存自定义的 config
* @param newConfig
*/
save (newConfig) {
// 对比默认config的异同
const defConfig = configApi.cloneDefault()
// 如果开启了远程配置,则读取远程配置,合并到默认配置中
if (get().app.remoteConfig.enabled === true) {
if (get().app.remoteConfig.url) {
mergeApi.doMerge(defConfig, configLoader.getRemoteConfig())
}
if (get().app.remoteConfig.personalUrl) {
mergeApi.doMerge(defConfig, configLoader.getRemoteConfig('_personal'))
}
}
// 计算新配置与默认配置(启用远程配置时,含远程配置)的差异
const diffConfig = mergeApi.doDiff(defConfig, newConfig)
// 将差异作为用户配置保存到 config.json 中
const configPath = configLoader.getUserConfigPath()
try {
fs.writeFileSync(configPath, jsonApi.stringify(diffConfig))
log.info('保存 config.json 自定义配置文件成功:', configPath)
} catch (e) {
log.error('保存 config.json 自定义配置文件失败:', configPath, ', error:', e)
throw e
}
// 重载配置
const allConfig = configApi.set(diffConfig)
return {
diffConfig,
allConfig,
}
},
doMerge: mergeApi.doMerge,
doDiff: mergeApi.doDiff,
/**
* 读取 config.json 后,合并配置
*/
reload () {
const userConfig = configLoader.getUserConfig()
return configApi.set(userConfig) || {}
},
update (partConfig) {
const newConfig = lodash.merge(configApi.get(), partConfig)
configApi.save(newConfig)
},
get,
set (newConfig) {
if (newConfig == null) {
log.warn('newConfig 为空,不做任何操作')
return configTarget
}
return configApi.load(newConfig)
},
load (newConfig) {
const config = configLoader.getConfigFromFiles(newConfig, defConfig)
configTarget = config
return config
},
cloneDefault () {
return lodash.cloneDeep(defConfig)
},
addDefault (key, defValue) {
lodash.set(defConfig, key, defValue)
},
// 移除用户配置,用于恢复出厂设置功能
async removeUserConfig () {
const configPath = configLoader.getUserConfigPath()
if (fs.existsSync(configPath)) {
// 读取 config.json 文件内容
const fileOriginalStr = fs.readFileSync(configPath).toString()
// 判断文件内容是否为空或空配置
const fileStr = fileOriginalStr.replace(/\s/g, '')
if (fileStr.length < 5) {
try {
fs.writeFileSync(configPath, '{}')
} catch (e) {
log.warn('简化用户配置文件失败:', configPath, ', error:', e)
}
return false // config.json 内容为空,或为空json
}
// 备份用户自定义配置文件
const bakConfigPath = `${configPath}.${Date.now()}.bak.json`
try {
fs.writeFileSync(bakConfigPath, fileOriginalStr)
log.info('备份用户配置文件成功:', bakConfigPath)
} catch (e) {
log.error('备份用户配置文件失败:', bakConfigPath, ', error:', e)
throw e
}
// 原配置文件内容设为空
try {
fs.writeFileSync(configPath, '{}')
} catch (e) {
log.error('初始化用户配置文件失败:', configPath, ', error:', e)
throw e
}
// 重新加载配置
configApi.load(null)
return true // 删除并重新加载配置成功
} else {
return false // config.json 文件不存在
}
},
resetDefault (key) {
if (key) {
let value = lodash.get(defConfig, key)
value = lodash.cloneDeep(value)
lodash.set(configTarget, key, value)
} else {
configTarget = lodash.cloneDeep(defConfig)
}
return configTarget
},
async getVariables (type) {
const method = type === 'npm' ? Shell.getNpmEnv : Shell.getSystemEnv
const currentMap = await method()
const list = []
const map = configTarget.variables[type]
for (const key in map) {
const exists = currentMap[key] != null
list.push({
key,
value: map[key],
exists,
})
}
return list
},
async setVariables (type) {
const list = await configApi.getVariables(type)
const noSetList = list.filter((item) => {
return !item.exists
})
if (list.length > 0) {
const context = {
root_ca_cert_path: configApi.get().server.setting.rootCaFile.certPath,
}
for (const item of noSetList) {
if (item.value.includes('${')) {
for (const key in context) {
item.value = item.value.replcace(new RegExp(`\${${key}}`, 'g'), context[key])
}
}
}
const method = type === 'npm' ? Shell.setNpmEnv : Shell.setSystemEnv
return method({ list: noSetList })
}
},
}
module.exports = configApi
================================================
FILE: packages/core/src/event.js
================================================
const listener = {}
let index = 1
function register (channel, handle, order = 10) {
let handles = listener[channel]
if (handles == null) {
handles = listener[channel] = []
}
handles.push({ id: index, handle, order })
handles.sort((a, b) => {
return a.order - b.order
})
return index++
}
function fire (channel, event) {
const handles = listener[channel]
if (handles == null) {
return
}
for (const item of handles) {
item.handle(event)
}
}
function unregister (id) {
for (const key in listener) {
const handlers = listener[key]
for (let i = 0; i < handlers.length; i++) {
const handle = handlers[i]
if (handle.id === id) {
handlers.splice(i)
return
}
}
}
}
const EventHub = {
register,
fire,
unregister,
}
module.exports = EventHub
================================================
FILE: packages/core/src/expose.js
================================================
const lodash = require('lodash')
const config = require('./config-api')
const event = require('./event')
const modules = require('./modules')
const shell = require('./shell')
const status = require('./status')
const log = require('./utils/util.log.core')
const context = {
config,
shell,
status,
event,
log,
}
function setupPlugin (key, plugin, context, config) {
const pluginConfig = plugin.config
const PluginClass = plugin.plugin
const pluginStatus = plugin.status
const api = PluginClass(context)
config.addDefault(key, pluginConfig)
if (pluginStatus) {
lodash.set(status, key, pluginStatus)
}
return api
}
const proxy = setupPlugin('proxy', modules.proxy, context, config)
const plugin = {}
for (const key in modules.plugin) {
const target = modules.plugin[key]
const api = setupPlugin(`plugin.${key}`, target, context, config)
plugin[key] = api
}
config.resetDefault()
const server = modules.server
const serverStart = server.start
function newServerStart ({ mitmproxyPath }) {
return serverStart({ mitmproxyPath, plugins: plugin })
}
server.start = newServerStart
async function startup ({ mitmproxyPath }) {
const conf = config.get()
if (conf.server.enabled) {
try {
await server.start({ mitmproxyPath })
} catch (err) {
log.error('代理服务启动失败:', err)
}
}
if (conf.proxy.enabled) {
try {
await proxy.start()
} catch (err) {
log.error('开启系统代理失败:', err)
}
}
try {
const plugins = []
for (const key in plugin) {
if (conf.plugin[key].enabled) {
const start = async () => {
try {
await plugin[key].start()
log.info(`插件【${key}】已启动`)
} catch (err) {
log.error(`插件【${key}】启动失败:`, err)
}
}
plugins.push(start())
}
}
if (plugins && plugins.length > 0) {
await Promise.all(plugins)
}
} catch (err) {
log.error('开启插件失败:', err)
}
}
async function shutdown () {
try {
const plugins = []
for (const key in plugin) {
if (status.plugin[key] && status.plugin[key].enabled && plugin[key].close) {
const close = async () => {
try {
await plugin[key].close()
log.info(`插件【${key}】已关闭`)
} catch (err) {
log.error(`插件【${key}】关闭失败:`, err)
}
}
plugins.push(close())
}
}
if (plugins.length > 0) {
await Promise.all(plugins)
}
} catch (error) {
log.error('插件关闭失败:', error)
}
if (status.proxy.enabled) {
try {
await proxy.close()
log.info('系统代理已关闭')
} catch (err) {
log.error('系统代理关闭失败:', err)
}
}
if (status.server.enabled) {
try {
await server.close()
log.info('代理服务已关闭')
} catch (err) {
log.error('代理服务关闭失败:', err)
}
}
}
const api = {
startup,
shutdown,
status: {
get () {
return status
},
},
config,
event,
shell,
server,
proxy,
plugin,
log,
}
module.exports = {
status,
api,
}
================================================
FILE: packages/core/src/index.js
================================================
const expose = require('./expose.js')
const log = require('./utils/util.log.core')
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
// 避免异常崩溃
process.on('uncaughtException', (err) => {
log.error('Process Uncaught Exception:', err)
})
process.on('unhandledRejection', (reason, p) => {
log.error('Process Unhandled Rejection at: Promise:', p, ', reason:', reason)
// application specific logging, throwing an error, or other logic here
})
module.exports = expose
================================================
FILE: packages/core/src/merge.js
================================================
const lodash = require('lodash')
/**
* 找出 newObj 相对于 oldObj 有差异的部分
*
* @param oldObj
* @param newObj
* @returns {{}|*}
*/
function doDiff (oldObj, newObj) {
if (newObj == null) {
return oldObj
}
// 临时的对象,用于找出被删除的数据
const tempObj = { ...oldObj }
// 删除空项,使差异对象更干净一些,体现出用户自定义内容
deleteNullItems(tempObj)
// 保存差异的对象
const diffObj = {}
// 读取新对象,并解析
for (const key in newObj) {
const newValue = newObj[key]
const oldValue = oldObj[key]
// 新值不为空,旧值为空时,直接取新值
if (newValue != null && oldValue == null) {
diffObj[key] = newValue
continue
}
// 新旧值相等时,忽略
if (lodash.isEqual(newValue, oldValue)) {
delete tempObj[key]
continue
}
// 新的值为数组时,直接取新值
if (lodash.isArray(newValue)) {
diffObj[key] = newValue
delete tempObj[key]
continue
}
// 新的值为对象时,递归合并
if (lodash.isObject(newValue)) {
diffObj[key] = doDiff(oldValue, newValue)
delete tempObj[key]
continue
}
// 基础类型,直接覆盖
delete tempObj[key]
diffObj[key] = newValue
}
// tempObj 里面剩下的是被删掉的数据
lodash.forEach(tempObj, (oldValue, key) => {
// 将被删除的属性设置为null,目的是为了merge时,将被删掉的对象设置为null,达到删除的目的
diffObj[key] = null
})
return diffObj
}
function deleteNullItems (target) {
lodash.forEach(target, (item, key) => {
if (item == null || item === '[delete]') {
delete target[key]
}
if (lodash.isObject(item)) {
deleteNullItems(item)
}
})
}
module.exports = {
doMerge (oldObj, newObj) {
return lodash.mergeWith(oldObj, newObj, (objValue, srcValue) => {
if (lodash.isArray(objValue)) {
return srcValue
}
})
},
doDiff,
deleteNullItems,
}
================================================
FILE: packages/core/src/modules/index.js
================================================
module.exports = {
server: require('./server'),
proxy: require('./proxy'),
plugin: require('./plugin'),
}
================================================
FILE: packages/core/src/modules/plugin/git/config.js
================================================
module.exports = {
name: 'Git.exe代理',
enabled: false,
tip: '如果你没有安装git命令行则不需要启动它',
setting: {
sslVerify: true, // Git.exe 是否关闭sslVerify,true=关闭 false=开启
noProxyUrls: {
'https://gitee.com': true, // 码云
'https://e.coding.net': true, // Coding(腾讯云)
'https://codeup.aliyun.com': true, // 云效 Codeup (阿里云)
},
},
}
================================================
FILE: packages/core/src/modules/plugin/git/index.js
================================================
const pluginConfig = require('./config')
const Plugin = function (context) {
const { config, shell, event, log } = context
const pluginApi = {
async start () {
const ip = '127.0.0.1'
const port = config.get().server.port
await pluginApi.setProxy(ip, port)
return { ip, port }
},
async close () {
return pluginApi.unsetProxy()
},
async restart () {
await pluginApi.close()
await pluginApi.start()
},
isEnabled () {
return config.get().plugin.git.enabled
},
async save (newConfig) {
},
async setProxy (ip, port) {
const cmds = [
`git config --global http.proxy http://${ip}:${port - 1} `,
`git config --global https.proxy http://${ip}:${port} `,
]
if (config.get().plugin.git.setting.sslVerify === true) {
cmds.push('git config --global http.sslVerify false ')
}
if (config.get().plugin.git.setting.noProxyUrls != null) {
for (const url in config.get().plugin.git.setting.noProxyUrls) {
cmds.push(`git config --global http."${url}".proxy "" `)
}
}
const ret = await shell.exec(cmds, { type: 'cmd' })
event.fire('status', { key: 'plugin.git.enabled', value: true })
log.info('开启【Git】代理成功')
return ret
},
// 当手动修改过 `~/.gitconfig` 时,`unset` 可能会执行失败,所以除了第一条命令外,其他命令都添加了try-catch,防止关闭Git代理失败
async unsetProxy () {
const ret = await shell.exec(['git config --global --unset http.proxy '], { type: 'cmd' })
try {
await shell.exec(['git config --global --unset https.proxy '], { type: 'cmd' })
} catch {
}
if (config.get().plugin.git.setting.sslVerify === true) {
try {
await shell.exec(['git config --global --unset http.sslVerify '], { type: 'cmd' })
} catch {
}
}
if (config.get().plugin.git.setting.noProxyUrls != null) {
for (const url in config.get().plugin.git.setting.noProxyUrls) {
try {
await shell.exec([`git config --global --unset http."${url}".proxy `], { type: 'cmd' })
} catch {
}
}
}
event.fire('status', { key: 'plugin.git.enabled', value: false })
log.info('关闭【Git】代理成功')
return ret
},
}
return pluginApi
}
module.exports = {
key: 'git',
config: pluginConfig,
status: {
enabled: false,
},
plugin: Plugin,
}
================================================
FILE: packages/core/src/modules/plugin/index.js
================================================
module.exports = {
node: require('./node'),
git: require('./git'),
pip: require('./pip'),
overwall: require('./overwall'),
}
================================================
FILE: packages/core/src/modules/plugin/node/config.js
================================================
module.exports = {
name: 'NPM加速',
enabled: false,
tip: '如果你没有安装nodejs则不需要启动它',
startup: {
variables: true,
},
setting: {
'command': 'npm',
'strict-ssl': true,
'cafile': false,
'NODE_EXTRA_CA_CERTS': false,
'NODE_TLS_REJECT_UNAUTHORIZED': false,
'yarnRegistry': 'default',
'registry': 'https://registry.npmjs.org', // 可以选择切换官方或者淘宝镜像
},
variables: {
phantomjs_cdnurl: 'https://npmmirror.com/mirrors/phantomjs',
chromedriver_cdnurl: 'https://npmmirror.com/mirrors/chromedriver',
sass_binary_site: 'https://npmmirror.com/mirrors/node-sass',
ELECTRON_MIRROR: 'https://npmmirror.com/mirrors/electron/',
NVM_NODEJS_ORG_MIRROR: 'https://npmmirror.com/mirrors/node',
CHROMEDRIVER_CDNURL: 'https://npmmirror.com/mirrors/chromedriver',
OPERADRIVER: 'https://npmmirror.com/mirrors/operadriver',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://npmmirror.com/mirrors/electron-builder-binaries/',
PYTHON_MIRROR: 'https://npmmirror.com/mirrors/python',
},
}
================================================
FILE: packages/core/src/modules/plugin/node/index.js
================================================
const jsonApi = require('@docmirror/mitmproxy/src/json')
const nodeConfig = require('./config')
const NodePlugin = function (context) {
const { config, shell, event, log } = context
const nodeApi = {
async start () {
try {
await nodeApi.setVariables()
} catch (err) {
log.warn('set variables error:', err)
}
const ip = '127.0.0.1'
const port = config.get().server.port
await nodeApi.setProxy(ip, port)
return { ip, port }
},
async close () {
return nodeApi.unsetProxy()
},
async restart () {
await nodeApi.close()
await nodeApi.start()
},
async save (newConfig) {
nodeApi.setVariables()
},
async getNpmEnv () {
const command = config.get().plugin.node.setting.command || 'npm'
const ret = await shell.exec([`${command} config list --json`], { type: 'cmd' })
if (ret != null) {
const json = ret.substring(ret.indexOf('{'))
return jsonApi.parse(json)
}
return {}
},
async setNpmEnv (list) {
const command = config.get().plugin.node.setting.command || 'npm'
const cmds = []
for (const item of list) {
if (item.value != null && item.value.length > 0 && item.value !== 'default' && item.value !== 'null') {
cmds.push(`${command} config set ${item.key} ${item.value}`)
} else {
cmds.push(`${command} config delete ${item.key}`)
}
}
return await shell.exec(cmds, { type: 'cmd' })
},
async unsetNpmEnv (list) {
const command = config.get().plugin.node.setting.command || 'npm'
const cmds = []
for (const item of list) {
cmds.push(`${command} config delete ${item} `)
}
return await shell.exec(cmds, { type: 'cmd' })
},
async setYarnEnv (list) {
const cmds = []
log.debug('yarn set:', JSON.stringify(list))
for (const item of list) {
if (item.value != null && item.value.length > 0 && item.value !== 'default' && item.value !== 'null') {
cmds.push(`yarn config set ${item.key} ${item.value}`)
} else {
cmds.push(`yarn config delete ${item.key}`)
}
}
return await shell.exec(cmds, { type: 'cmd' })
},
async unsetYarnEnv (list) {
const cmds = []
for (const item of list) {
cmds.push(`yarn config delete ${item} `)
}
return await shell.exec(cmds, { type: 'cmd' })
},
async getVariables () {
const currentMap = await nodeApi.getNpmEnv()
const list = []
const map = config.get().plugin.node.variables
for (const key in map) {
const exists = currentMap[key] != null
list.push({
key,
value: map[key],
oldValue: currentMap[key],
exists,
hadSet: currentMap[key] === map[key],
})
}
return list
},
async setVariables () {
const list = await nodeApi.getVariables()
const noSetList = list.filter((item) => {
return !item.exists
})
if (noSetList.length > 0) {
return nodeApi.setNpmEnv(noSetList)
}
},
async setRegistry ({ registry, type }) {
if (type === 'npm') {
await nodeApi.setNpmEnv([{ key: 'registry', value: registry }])
} else {
await nodeApi.setYarnEnv([{ key: 'registry', value: registry }])
}
return true
},
async setProxy (ip, port) {
const command = config.get().plugin.node.setting.command || 'npm'
const cmds = [
`${command} config set proxy=http://${ip}:${port - 1}`,
`${command} config set https-proxy=http://${ip}:${port}`,
]
const env = []
/**
* 'strict-ssl': false,
cafile: true,
NODE_EXTRA_CA_CERTS: true,
NODE_TLS_REJECT_UNAUTHORIZED: false
*/
const nodeConfig = config.get().plugin.node
const rootCaCertFile = config.get().server.setting.rootCaFile.certPath
if (nodeConfig.setting['strict-ssl']) {
cmds.push(`${command} config set strict-ssl false`)
}
if (nodeConfig.setting.cafile) {
cmds.push(`${command} config set cafile "${rootCaCertFile}"`)
}
if (nodeConfig.setting.NODE_EXTRA_CA_CERTS) {
cmds.push(`${command} config set NODE_EXTRA_CA_CERTS "${rootCaCertFile}"`)
env.push({ key: 'NODE_EXTRA_CA_CERTS', value: rootCaCertFile })
}
if (nodeConfig.setting.NODE_TLS_REJECT_UNAUTHORIZED) {
cmds.push(`${command} config set NODE_TLS_REJECT_UNAUTHORIZED 0`)
env.push({ key: 'NODE_TLS_REJECT_UNAUTHORIZED', value: '0' })
}
const ret = await shell.exec(cmds, { type: 'cmd' })
if (env.length > 0) {
await shell.setSystemEnv({ list: env })
}
event.fire('status', { key: 'plugin.node.enabled', value: true })
log.info('开启【NPM】代理成功')
return ret
},
async unsetProxy () {
const command = config.get().plugin.node.setting.command || 'npm'
const cmds = [
`${command} config delete proxy`,
`${command} config delete https-proxy`,
`${command} config delete NODE_EXTRA_CA_CERTS`,
`${command} config delete strict-ssl`,
]
const ret = await shell.exec(cmds, { type: 'cmd' })
event.fire('status', { key: 'plugin.node.enabled', value: false })
log.info('关闭【NPM】代理成功')
return ret
},
}
return nodeApi
}
module.exports = {
key: 'node',
config: nodeConfig,
status: {
enabled: false,
},
plugin: NodePlugin,
}
================================================
FILE: packages/core/src/modules/plugin/overwall/config.js
================================================
module.exports = {
name: '梯子',
enabled: false, // 默认关闭梯子
server: {},
serverDefault: {
'ow-prod.docmirror.top': {
port: 443,
path: 'X2dvX292ZXJfd2FsbF8',
password: 'dev_sidecar_is_666',
},
},
targets: {
'*.github.com': true,
'*github*.com': true,
'*.wikimedia.org': true,
'*.v2ex.com': true,
'*.azureedge.net': true,
'*.cloudfront.net': true,
'*.bing.com': true,
'*.discourse-cdn.com': true,
'*.gravatar.com': true,
'*.docker.com': true,
'*.vueuse.org': true,
'*.elastic.co': true,
'*.optimizely.com': true,
'*.stackpathcdn.com': true,
'*.fastly.net': true,
'*.cloudflare.com': true,
'*.233v2.com': true,
'*.v2fly.org': true,
'*.telegram.org': true,
'*.amazon.com': true,
'*.googleapis.com': true,
'*.google-analytics.com': true,
'*.cloudflareinsights.com': true,
'*.intlify.dev': true,
'*.segment.io': true,
'*.shields.io': true,
'*.jsdelivr.net': true,
},
pac: {
enabled: true,
autoUpdate: true,
pacFileUpdateUrl: 'https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt',
pacFileAbsolutePath: null, // 自定义 pac.txt 文件位置,可以是本地文件路径
pacFilePath: './extra/pac/pac.txt', // 内置 pac.txt 文件路径
},
}
================================================
FILE: packages/core/src/modules/plugin/overwall/index.js
================================================
const pluginConfig = require('./config')
const Plugin = function (context) {
const { config, shell, event, log } = context
const api = {
async start () {
// event.fire('status', { key: 'plugin.overwall.enabled', value: true })
},
async close () {
// event.fire('status', { key: 'plugin.overwall.enabled', value: false })
},
async restart () {
await api.close()
await api.start()
},
async overrideRunningConfig_bak (serverConfig) {
const conf = config.get().plugin.overwall
if (!conf || !conf.enabled || !conf.targets) {
return
}
const server = conf.server
let i = 0
let main
const backup = []
for (const key in server) {
if (i === 0) {
main = key
} else {
backup.push(key)
}
i++
}
for (const key in conf.targets) {
serverConfig.intercepts[key] = {
'.*': {
proxy: `${main}/\${host}`,
backup,
},
}
}
},
}
return api
}
module.exports = {
key: 'overwall',
config: pluginConfig,
plugin: Plugin,
}
================================================
FILE: packages/core/src/modules/plugin/pip/config.js
================================================
module.exports = {
name: 'PIP加速',
statusOff: true,
enabled: null, // 没有开关
tip: '如果你没有安装pip则不需要启动它',
startup: {
},
setting: {
command: 'pip',
trustedHost: 'pypi.org',
registry: 'https://pypi.org/simple/', // 可以选择切换官方或者淘宝镜像
},
}
================================================
FILE: packages/core/src/modules/plugin/pip/index.js
================================================
const pipConfig = require('./config')
const PipPlugin = function (context) {
const { config, shell, event, log } = context
const api = {
async start () {
await api.setRegistry({ registry: config.get().plugin.pip.setting.registry })
await api.setTrustedHost(config.get().plugin.pip.setting.trustedHost)
},
async close () {
},
async restart () {
await api.close()
await api.start()
},
async save (newConfig) {
await api.setVariables()
},
async getPipEnv () {
const command = config.get().plugin.pip.setting.command
let ret = await shell.exec([`${command} config list`], { type: 'cmd' })
if (ret != null) {
ret = ret.trim()
const lines = ret.split('\n')
const vars = {}
for (const line of lines) {
if (!line.startsWith('global')) {
continue
}
const key = line.substring(0, line.indexOf('='))
let value = line.substring(line.indexOf('=') + 1)
if (value.startsWith('\'')) {
value = value.startsWith(1, value.length - 1)
}
vars[key] = value
}
return vars
}
return {}
},
async setPipEnv (list) {
const command = config.get().plugin.pip.setting.command
const cmds = []
for (const item of list) {
if (item.value != null) {
cmds.push(`${command} config set global.${item.key} ${item.value}`)
} else {
cmds.push(`${command} config unset global.${item.key}`)
}
}
return await shell.exec(cmds, { type: 'cmd' })
},
async unsetPipEnv (list) {
const command = config.get().plugin.pip.setting.command
const cmds = []
for (const item of list) {
cmds.push(`${command} config unset global.${item} `)
}
return await shell.exec(cmds, { type: 'cmd' })
},
async setRegistry ({ registry }) {
await api.setPipEnv([{ key: 'index-url', value: registry }])
return true
},
async setTrustedHost (host) {
await api.setPipEnv([{ key: 'trusted-host', value: host }])
return true
},
async setProxy (ip, port) {
},
async unsetProxy () {
},
}
return api
}
module.exports = {
key: 'pip',
config: pipConfig,
status: {
enabled: false,
},
plugin: PipPlugin,
}
================================================
FILE: packages/core/src/modules/proxy/index.js
================================================
const ProxyPlugin = function (context) {
const { config, event, shell, log } = context
const api = {
async start () {
return api.setProxy()
},
async close () {
return api.unsetProxy()
},
async restart () {
await api.close()
await api.start()
},
async setProxy () {
const ip = '127.0.0.1'
const port = config.get().server.port
const setEnv = config.get().proxy.setEnv
await shell.setSystemProxy({ ip, port, setEnv })
log.info(`开启系统代理成功:${ip}:${port}`)
event.fire('status', { key: 'proxy.enabled', value: true })
return { ip, port }
},
async unsetProxy (setEnv) {
if (setEnv) {
setEnv = config.get().proxy.setEnv
}
try {
await shell.setSystemProxy({ setEnv })
event.fire('status', { key: 'proxy.enabled', value: false })
log.info('关闭系统代理成功')
return true
} catch (err) {
log.error('关闭系统代理失败:', err)
return false
}
},
async setEnableLoopback () {
await shell.enableLoopback()
log.info('打开EnableLoopback成功')
return true
},
}
return api
}
module.exports = {
key: 'proxy',
config: {
enabled: true,
name: '系统代理',
use: 'local',
other: [],
proxyHttp: false, // false=只代理HTTPS请求 true=同时代理HTTP和HTTPS请求
setEnv: false,
// 排除国内域名 所需配置
excludeDomesticDomainAllowList: true, // 是否排除国内域名,默认:需要排除
autoUpdateDomesticDomainAllowList: true, // 是否自动更新国内域名
remoteDomesticDomainAllowListFileUrl: 'https://raw.githubusercontent.com/pluwen/china-domain-allowlist/refs/heads/main/allow-list.sorl',
domesticDomainAllowListFileAbsolutePath: null, // 自定义 domestic-domain-allowlist.txt 文件位置,可以是本地文件路径
domesticDomainAllowListFilePath: './extra/proxy/domestic-domain-allowlist.txt', // 内置国内域名文件
// 自定义系统代理排除列表
excludeIpList: {
// region 常用国内可访问域名
// 中国大陆
'*.cn': true,
'cn.*': true,
'*china*': true,
// Github加速源:以下加速源代理后反而出现问题,从系统代理中排除掉
'*.kkgithub.com': true,
'*.ghproxy.*': true,
// Github ssh
'ssh.github.com': true,
// DeepL
'www.deepl.com': true,
// CSDN
'*.csdn.net': true,
// 360 so
'*.so.com': true,
// 百度
'*.baidu.com': true,
'*.baiducontent.com': true,
'*.bdimg.com': true,
'*.bdstatic.com': true,
'*.bdydns.com': true,
// 腾讯
'*.tencent.com': true,
'*.qq.com': true,
'*.weixin.com': true,
'*.weixinbridge.com': true,
'*.wechat.com': true,
'*.idqqimg.com': true,
'*.gtimg.com': true,
'*.qpic.com': true,
'*.qlogo.com': true,
'*.myapp.com': true,
// 阿里
'*.aliyun.com': true,
'*.alipay.com': true,
'*.taobao.com': true,
'*.tmall.com': true,
'*.alipayobjects.com': true,
'*.dingtalk.com': true,
'*.mmstat.com': true,
'*.alicdn.com': true,
'*.hdslb.com': true,
// Gitee
'gitee.com': true,
'*.gitee.com': true,
'*.gitee.io': true,
'*.giteeusercontent.com': true,
// Mozilla Firefox
'*.mozilla.org': true,
'*.mozilla.com': true,
'*.mozilla.net': true,
'*.firefox.com': true,
'*.firefox.org': true,
'*.mozillademos.org': true,
'*.mozillians.org': true,
'*.mozillians.net': true,
'*.mozillians.com': true,
// OSS
'*.sonatype.org': true,
// Maven镜像
'*.maven.org': true,
// Maven Repository
'*.mvnrepository.com': true,
// 苹果
'*.apple.com': true,
'*.icloud.com': true,
// 微软
'*.microsoft.com': true,
'*.windows.com': true,
'*.office.com': true,
'*.office.net': true,
'*.live.com': true,
'*.msn.com': true,
// WPS
'*.wps.com': true,
'*.wps.net': true,
'*.ksord.com': true,
// 奇虎
'*.qihoo.com': true,
'*.qihucdn.com': true,
// 360
'*.360.com': true,
'*.360safe.com': true,
'*.360buyimg.com': true,
'*.360buy.com': true,
// 京东
'*.jd.com': true,
'*.jcloud.com': true,
'*.jcloudcs.com': true,
'*.jcloudcache.com': true,
'*.jcloudcdn.com': true,
'*.jcloudlb.com': true,
// 哔哩哔哩
'*.bilibili.com': true,
'*.bilivideo.com': true,
'*.biliapi.net': true,
// 移动
'*.10086.com': true,
'*.10086cloud.com': true,
// 移动:139邮箱
'*.139.com': true,
// 迅雷
'*.xunlei.com': true,
// 网站ICP备案查询
'*.icpapi.com': true,
// Navicat
'*.navicat.com': true,
// Github文件上传所使用的域名,被DS代理会导致文件上传经常失败,从系统代理中排除掉
'objects-origin.githubusercontent.com': true,
// cloudflare:排除以下域名,cloudflare的人机校验会更快,成功率更高。
'challenges.cloudflare.com': true,
// endregion
// 本地地址,无需代理
'localhost': true,
'localhost.*': true, // 部分VPN会在host中添加这种格式的域名指向127.0.0.1,所以也排除掉
'127.*.*.*': true,
'test.*': true, // 本地开发时,测试用的虚拟域名格式,无需代理
// 服务器端常用地址,无需代理
'10.*.*.*': true,
'172.16.*.*': true,
'172.17.*.*': true,
'172.18.*.*': true,
'172.19.*.*': true,
'172.20.*.*': true,
'172.21.*.*': true,
'172.22.*.*': true,
'172.23.*.*': true,
'172.24.*.*': true,
'172.25.*.*': true,
'172.26.*.*': true,
'172.27.*.*': true,
'172.28.*.*': true,
'172.29.*.*': true,
'172.30.*.*': true,
'172.31.*.*': true,
// 局域网地址,无需代理
'192.168.*.*': true,
},
},
status: {
enabled: false,
proxyTarget: '',
},
plugin: ProxyPlugin,
}
================================================
FILE: packages/core/src/modules/server/index.js
================================================
const fork = require('node:child_process').fork
const fs = require('node:fs')
const path = require('node:path')
const lodash = require('lodash')
const config = require('../../config-api')
const event = require('../../event')
const status = require('../../status')
const jsonApi = require('@docmirror/mitmproxy/src/json')
const log = require('../../utils/util.log.core')
let server = null
function fireStatus (status) {
event.fire('status', { key: 'server.enabled', value: status })
}
function sleep (time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}
const serverApi = {
async startup () {
if (config.get().server.startup) {
return this.start(config.get().server)
}
},
async shutdown () {
if (status.server) {
return this.close()
}
},
async start ({ mitmproxyPath, plugins }) {
const allConfig = config.get()
const serverConfig = lodash.cloneDeep(allConfig.server)
const intercepts = serverConfig.intercepts
const dnsMapping = serverConfig.dns.mapping
if (allConfig.plugin) {
lodash.each(allConfig.plugin, (value) => {
const plugin = value
if (!plugin.enabled) {
return
}
if (plugin.intercepts) {
lodash.merge(intercepts, plugin.intercepts)
}
if (plugin.dns) {
lodash.merge(dnsMapping, plugin.dns)
}
})
}
if (allConfig.app) {
serverConfig.app = allConfig.app
}
if (serverConfig.intercept.enabled === false) {
// 如果设置为关闭拦截
serverConfig.intercepts = {}
}
for (const key in plugins) {
const plugin = plugins[key]
if (plugin.overrideRunningConfig) {
plugin.overrideRunningConfig(serverConfig)
}
}
serverConfig.plugin = allConfig.plugin
if (allConfig.proxy && allConfig.proxy.enabled) {
serverConfig.proxy = allConfig.proxy
}
// fireStatus('ing') // 启动中
const basePath = serverConfig.setting.userBasePath
const runningConfigPath = path.join(basePath, '/running.json')
try {
fs.writeFileSync(runningConfigPath, jsonApi.stringify(serverConfig))
log.info('保存 running.json 运行时配置文件成功:', runningConfigPath)
} catch (e) {
log.error('保存 running.json 运行时配置文件失败:', runningConfigPath, ', error:', e)
throw e
}
const serverProcess = fork(mitmproxyPath, [runningConfigPath])
server = {
id: serverProcess.pid,
process: serverProcess,
close () {
serverProcess.send({ type: 'action', event: { key: 'close' } })
},
}
serverProcess.on('beforeExit', (code) => {
log.warn('server process beforeExit, code:', code)
})
serverProcess.on('SIGPIPE', (code, signal) => {
log.warn(`server process SIGPIPE, code: ${code}, signal:`, signal)
})
serverProcess.on('exit', (code, signal) => {
log.warn(`server process exit, code: ${code}, signal:`, signal)
})
serverProcess.on('uncaughtException', (err, origin) => {
log.error('server process uncaughtException:', err)
})
serverProcess.on('message', (msg) => {
log.debug('收到子进程消息:', JSON.stringify(msg))
if (msg.type === 'status') {
fireStatus(msg.event)
} else if (msg.type === 'error') {
let code = ''
if (msg.event.code) {
code = msg.event.code
}
fireStatus(false) // 启动失败
event.fire('error', { key: 'server', value: code, error: msg.event, message: msg.message })
} else if (msg.type === 'speed') {
event.fire('speed', msg.event)
}
})
return { port: serverConfig.port }
},
async kill () {
if (server) {
server.process.kill('SIGINT')
await sleep(1000)
}
fireStatus(false)
},
async close () {
return await serverApi.kill()
},
async close1 () {
return new Promise((resolve, reject) => {
if (server) {
// fireStatus('ing')// 关闭中
server.close((err) => {
if (err) {
log.warn('close error:', err)
if (err.code === 'ERR_SERVER_NOT_RUNNING') {
log.info('代理服务关闭成功')
resolve()
return
}
log.warn('代理服务关闭失败:', err)
reject(err)
} else {
log.info('代理服务关闭成功')
resolve()
}
})
} else {
log.info('server is null')
resolve()
}
})
},
async restart ({ mitmproxyPath }) {
await serverApi.kill()
await serverApi.start({ mitmproxyPath })
},
getServer () {
return server
},
getSpeedTestList () {
if (server) {
server.process.send({ type: 'speed', event: { key: 'getList' } })
}
},
reSpeedTest () {
if (server) {
server.process.send({ type: 'speed', event: { key: 'reTest' } })
}
},
}
module.exports = serverApi
================================================
FILE: packages/core/src/shell/index.js
================================================
const enableLoopback = require('./scripts/enable-loopback')
const extraPath = require('./scripts/extra-path')
const getNpmEnv = require('./scripts/get-npm-env')
const getSystemEnv = require('./scripts/get-system-env')
const killByPort = require('./scripts/kill-by-port')
const setNpmEnv = require('./scripts/set-npm-env')
const setSystemEnv = require('./scripts/set-system-env')
const setSystemProxy = require('./scripts/set-system-proxy')
const setupCa = require('./scripts/setup-ca')
const shell = require('./shell')
module.exports = {
killByPort,
setupCa,
getSystemEnv,
setSystemEnv,
getNpmEnv,
setNpmEnv,
setSystemProxy,
enableLoopback,
extraPath,
async exec (cmds, args) {
return shell.getSystemShell().exec(cmds, args)
},
getSystemPlatform: shell.getSystemPlatform,
}
================================================
FILE: packages/core/src/shell/scripts/enable-loopback.js
================================================
/**
*/
const Shell = require('../shell')
const extraPath = require('./extra-path')
const sudoPrompt = require('@vscode/sudo-prompt')
const log = require('../../utils/util.log.core')
const execute = Shell.execute
const executor = {
windows (exec) {
const loopbackPath = extraPath.getEnableLoopbackPath()
const sudoCommand = [`"${loopbackPath}"`]
const options = {
name: 'EnableLoopback',
}
return new Promise((resolve, reject) => {
sudoPrompt.exec(
sudoCommand.join(' '),
options,
(error, _, stderr) => {
if (stderr) {
log.error(`[sudo-prompt] 发生错误: ${stderr}`)
}
if (error) {
reject(error)
} else {
resolve(undefined)
}
},
)
})
},
async linux (exec, { port }) {
throw new Error('不支持此操作')
},
async mac (exec, { port }) {
throw new Error('不支持此操作')
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/extra-path/index.js
================================================
const path = require('node:path')
const log = require('../../../utils/util.log.core')
function getExtraPath () {
let extraPath = process.env.DS_EXTRA_PATH
log.info('extraPath:', extraPath)
if (!extraPath) {
extraPath = __dirname
}
return extraPath
}
function getProxyExePath () {
const extraPath = getExtraPath()
return path.join(extraPath, 'sysproxy.exe')
}
function getEnableLoopbackPath () {
const extraPath = getExtraPath()
return path.join(extraPath, 'EnableLoopback.exe')
}
module.exports = {
getProxyExePath,
getEnableLoopbackPath,
}
================================================
FILE: packages/core/src/shell/scripts/get-npm-env.js
================================================
/**
* 获取环境变量
*/
const jsonApi = require('@docmirror/mitmproxy/src/json')
const Shell = require('../shell')
const execute = Shell.execute
const executor = {
async windows (exec) {
const ret = await exec(['npm config list --json'], { type: 'cmd' })
if (ret != null) {
const json = ret.substring(ret.indexOf('{'))
return jsonApi.parse(json)
}
return {}
},
async linux (exec, { port }) {
throw new Error('暂未实现此功能')
},
async mac (exec, { port }) {
throw new Error('暂未实现此功能')
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/get-system-env.js
================================================
/**
* 获取环境变量
*/
const Shell = require('../shell')
const execute = Shell.execute
const executor = {
async windows (exec) {
const ret = await exec(['set'], { type: 'cmd' })
const map = {}
if (ret != null) {
const lines = ret.split('\r\n')
for (const item of lines) {
const kv = item.split('=')
if (kv.length > 1) {
map[kv[0].trim()] = kv[1].trim()
}
}
}
return map
},
async linux (exec, { port }) {
throw new Error('暂未实现此功能')
},
async mac (exec, { port }) {
throw new Error('暂未实现此功能')
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/kill-by-port.js
================================================
const Shell = require('../shell')
const execute = Shell.execute
const executor = {
async windows (exec, { port }) {
const cmds = [`for /f "tokens=5" %a in ('netstat -aon ^| find ":${port}" ^| find "LISTENING"') do (taskkill /f /pid %a & exit /B) `]
await exec(cmds, { type: 'cmd' })
return true
},
async linux (exec, { port }) {
await exec(`kill \`lsof -i:${port} |grep 'dev-sidecar\\|electron\\|@docmirro' |awk '{print $2}'\``)
return true
},
async mac (exec, { port }) {
await exec(`kill \`lsof -i:${port} |grep 'dev-side\\|Elect' |awk '{print $2}'\``)
return true
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/set-npm-env.js
================================================
/**
* 设置环境变量
*/
const Shell = require('../shell')
const execute = Shell.execute
const executor = {
async windows (exec, { list }) {
const cmds = []
for (const item of list) {
cmds.push(`npm config set ${item.key} ${item.value}`)
}
return await exec(cmds, { type: 'cmd' })
},
async linux (exec, { port }) {
throw new Error('暂未实现此功能')
},
async mac (exec, { port }) {
throw new Error('暂未实现此功能')
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/set-system-env.js
================================================
/**
* 设置环境变量
*/
const Shell = require('../shell')
const execute = Shell.execute
const executor = {
async windows (exec, { list }) {
const cmds = []
for (const item of list) {
// [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
cmds.push(`[Environment]::SetEnvironmentVariable('${item.key}', '${item.value}', 'Machine')`)
}
const ret = await exec(cmds, { type: 'ps' })
const cmds2 = []
for (const item of list) {
// [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
cmds2.push(`set ${item.key}=""`)
}
await exec(cmds2, { type: 'cmd' })
return ret
},
async linux (exec, { port }) {
throw new Error('暂未实现此功能')
},
async mac (exec, { port }) {
throw new Error('暂未实现此功能')
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/set-system-proxy/index.js
================================================
/**
* 获取环境变量
*/
const fs = require('node:fs')
const path = require('node:path')
const request = require('request')
const Registry = require('winreg')
const log = require('../../../utils/util.log.core')
const Shell = require('../../shell')
const extraPath = require('../extra-path')
const dateUtil = require('../../../utils/util.date')
const execute = Shell.execute
const execFile = Shell.execFile
let config = null
function loadConfig () {
if (config == null) {
config = require('../../../config-api.js')
}
}
function getDomesticDomainAllowListTmpFilePath () {
return path.join(config.get().server.setting.userBasePath, '/domestic-domain-allowlist.txt')
}
async function downloadDomesticDomainAllowListAsync () {
loadConfig()
const remoteFileUrl = config.get().proxy.remoteDomesticDomainAllowListFileUrl
log.info('开始下载远程 domestic-domain-allowlist.txt 文件:', remoteFileUrl)
request(remoteFileUrl, (error, response, body) => {
if (error) {
log.error(`下载远程 domestic-domain-allowlist.txt 文件失败: ${remoteFileUrl}, error:`, error, ', response:', response, ', body:', body)
return
}
if (response && response.statusCode === 200) {
if (body == null || body.length < 100) {
log.warn('下载远程 domestic-domain-allowlist.txt 文件成功,但内容为空或内容太短,判断为无效的 domestic-domain-allowlist.txt 文件:', remoteFileUrl, ', body:', body)
return
} else {
log.info('下载远程 domestic-domain-allowlist.txt 文件成功:', remoteFileUrl)
}
let fileTxt = body
try {
if (!fileTxt.includes('*.')) {
fileTxt = Buffer.from(fileTxt, 'base64').toString('utf8')
// log.debug('解析 base64 后的 domestic-domain-allowlist:', fileTxt)
}
} catch {
if (!fileTxt.includes('*.')) {
log.error(`远程 domestic-domain-allowlist.txt 文件内容即不是base64格式,也不是要求的格式,url: ${remoteFileUrl},body: ${body}`)
return
}
}
// 保存到本地
saveDomesticDomainAllowListFile(fileTxt)
} else {
log.error(`下载远程 domestic-domain-allowlist.txt 文件失败: ${remoteFileUrl}, response:`, response, ', body:', body)
}
})
}
function loadLastModifiedTimeFromTxt (fileTxt) {
const matched = fileTxt.match(/(?<=; Update Date: )[^\r\n]+/g)
if (matched && matched.length > 0) {
try {
return new Date(matched[0])
} catch {
return null
}
}
}
// 保存 国内域名白名单 内容到 `~/domestic-domain-allowlist.txt` 文件中
function saveDomesticDomainAllowListFile (fileTxt) {
const filePath = getDomesticDomainAllowListTmpFilePath()
try {
fs.writeFileSync(filePath, fileTxt.replaceAll(/\r\n?/g, '\n'))
log.info('保存 domestic-domain-allowlist.txt 文件成功:', filePath)
} catch (e) {
log.error('保存 domestic-domain-allowlist.txt 文件失败:', filePath, ', error:', e)
return
}
// 尝试解析和修改 domestic-domain-allowlist.txt 文件时间
const lastModifiedTime = loadLastModifiedTimeFromTxt(fileTxt)
if (lastModifiedTime) {
fs.stat(filePath, (err, _stats) => {
if (err) {
log.error('修改 domestic-domain-allowlist.txt 文件时间失败:', err)
return
}
// 修改文件的访问时间和修改时间为当前时间
fs.utimes(filePath, lastModifiedTime, lastModifiedTime, (utimesErr) => {
if (utimesErr) {
log.error('修改 domestic-domain-allowlist.txt 文件时间失败:', utimesErr)
} else {
log.info(`'${filePath}' 文件的修改时间已更新为其最近更新时间 '${dateUtil.format(lastModifiedTime, false)}'`)
}
})
})
}
}
function getDomesticDomainAllowList () {
loadConfig()
if (!config.get().proxy.excludeDomesticDomainAllowList) {
return null
}
// 判断是否需要自动更新国内域名
let fileAbsolutePath = config.get().proxy.domesticDomainAllowListFileAbsolutePath
if (!fileAbsolutePath && config.get().proxy.autoUpdateDomesticDomainAllowList) {
// 异步下载,下载成功后,下次系统代理生效
downloadDomesticDomainAllowListAsync().then()
}
// 加载本地文件
if (!fileAbsolutePath) {
const tmpFilePath = getDomesticDomainAllowListTmpFilePath()
if (fs.existsSync(tmpFilePath)) {
// 如果临时文件已存在,则使用临时文件
fileAbsolutePath = tmpFilePath
log.info('读取已下载的 domestic-domain-allowlist.txt 文件:', fileAbsolutePath)
} else {
// 如果临时文件不存在,则使用内置文件
log.info('__dirname:', __dirname)
fileAbsolutePath = path.join(__dirname, '../', config.get().proxy.domesticDomainAllowListFilePath)
log.info('读取内置的 domestic-domain-allowlist.txt 文件:', fileAbsolutePath)
}
} else {
log.info('读取自定义路径的 domestic-domain-allowlist.txt 文件:', fileAbsolutePath)
}
try {
return fs.readFileSync(fileAbsolutePath).toString()
} catch (e) {
log.error(`读取 domestic-domain-allowlist.txt 文件失败: ${fileAbsolutePath}, error:`, e)
return null
}
}
function getProxyExcludeIpStr (split) {
const proxyExcludeIpConfig = config.get().proxy.excludeIpList
let excludeIpStr = ''
for (const ip in proxyExcludeIpConfig) {
if (proxyExcludeIpConfig[ip] === true) {
excludeIpStr += ip + split
}
}
// 排除国内域名
// log.debug('系统代理排除域名(excludeIpStr):', excludeIpStr)
if (config.get().proxy.excludeDomesticDomainAllowList) {
try {
const domesticDomainAllowList = getDomesticDomainAllowList()
if (domesticDomainAllowList) {
const domesticDomainList = (`\n${domesticDomainAllowList}`).replaceAll(/[\r\n]+/g, '\n').match(/(?<=\n)(?:[\w\-.*]+|\[[\w:]+\])(?=\n)/g)
if (domesticDomainList && domesticDomainList.length > 0) {
for (const domesticDomain of domesticDomainList) {
if (proxyExcludeIpConfig[domesticDomain] !== false) {
excludeIpStr += domesticDomain + split
} else {
log.info('系统代理排除列表拼接国内域名时,跳过域名,系统代理将继续代理它:', domesticDomain)
}
}
log.info('系统代理排除列表拼接国内域名成功')
} else {
log.info('国内域名为空,不进行系统代理排除列表拼接国内域名')
}
}
} catch (e) {
log.error('系统代理排除列表拼接国内域名失败:', e)
}
}
return excludeIpStr
}
const executor = {
async windows (exec, params = {}) {
const { ip, port, setEnv } = params
if (ip != null) { // 设置代理
// 延迟加载config
loadConfig()
log.info('开始设置windows系统代理:', ip, port, setEnv)
// https
let proxyAddr = `https=http://${ip}:${port}`
// http
if (config.get().proxy.proxyHttp) {
proxyAddr = `http=http://${ip}:${port - 1};${proxyAddr}`
}
// 读取排除域名
const excludeIpStr = getProxyExcludeIpStr(';')
// 设置代理,同时设置排除域名
try {
require('@starknt/sysproxy').triggerManualProxyByUrl(true, proxyAddr, excludeIpStr, true)
log.info(`设置windows系统代理成功: ${proxyAddr} ......(省略排除IP列表)`)
} catch (e1) {
log.warn('设置windows系统代理失败:执行 `@starknt/sysproxy` 失败,现尝试通过执行 `sysproxy.exe global ...` 来设置系统代理!\r\n捕获的异常:', e1)
const proxyPath = extraPath.getProxyExePath()
const execFun = 'global'
try {
await execFile(proxyPath, [execFun, proxyAddr, excludeIpStr])
log.info(`设置windows系统代理成功,执行的命令:${proxyPath} ${execFun} ${proxyAddr} ......(省略排除IP列表)`)
} catch (e2) {
log.error(`设置windows系统代理失败,执行的命令:${proxyPath} ${execFun} ${proxyAddr} ......(省略排除IP列表), error:`, e2)
throw e1 // 将上面的异常抛出
}
}
if (setEnv) {
// 设置全局代理所需的环境变量
try {
await exec(`echo '设置环境变量 HTTPS_PROXY${config.get().proxy.proxyHttp ? '、HTTP_PROXY' : ''}'`)
log.info(`开启系统代理的同时设置环境变量:HTTPS_PROXY = "http://${ip}:${port}/"`)
await exec(`setx HTTPS_PROXY "http://${ip}:${port}/"`)
if (config.get().proxy.proxyHttp) {
log.info(`开启系统代理的同时设置环境变量:HTTP_PROXY = "http://${ip}:${port - 1}/"`)
await exec(`setx HTTP_PROXY "http://${ip}:${port - 1}/"`)
}
// await addClearScriptIni()
} catch (e) {
log.error('设置环境变量 HTTPS_PROXY、HTTP_PROXY 失败:', e)
}
}
return true
} else { // 关闭代理
try {
log.info('开始关闭windows系统代理')
require('@starknt/sysproxy').triggerManualProxy(false, '', 0, '')
log.info('关闭windows系统代理成功')
} catch (e1) {
log.error('关闭windows系统代理失败:执行 `@starknt/sysproxy` 失败,现尝试通过执行 `sysproxy.exe set 1` 来关闭系统代理!\r\n捕获的异常:', e1)
try {
const proxyPath = extraPath.getProxyExePath()
await execFile(proxyPath, ['set', '1'])
log.info('关闭windows系统代理成功,执行的命令:sysproxy.exe set 1')
} catch (e2) {
log.error('关闭windows系统代理失败,执行的命令:sysproxy.exe set 1, error:', e2)
throw e1 // 将上面的异常抛出
}
}
try {
await exec('echo \'删除环境变量 HTTPS_PROXY、HTTP_PROXY\'')
const regKey = new Registry({ // new operator is optional
hive: Registry.HKCU, // open registry hive HKEY_CURRENT_USER
key: '\\Environment', // key containing autostart programs
})
regKey.get('HTTPS_PROXY', (err) => {
if (!err) {
regKey.remove('HTTPS_PROXY', async (err) => {
log.warn('删除环境变量 HTTPS_PROXY 失败:', err)
await exec('setx DS_REFRESH "1"')
})
}
})
regKey.get('HTTP_PROXY', (err) => {
if (!err) {
regKey.remove('HTTP_PROXY', async (err) => {
log.warn('删除环境变量 HTTP_PROXY 失败:', err)
})
}
})
} catch (e) {
log.error('删除环境变量 HTTPS_PROXY、HTTP_PROXY 失败:', e)
}
return true
}
},
async linux (exec, params = {}) {
const { ip, port } = params
if (ip != null) { // 设置代理
// 延迟加载config
loadConfig()
// https
const setProxyCmd = [
'gsettings set org.gnome.system.proxy mode manual',
`gsettings set org.gnome.system.proxy.https host ${ip}`,
`gsettings set org.gnome.system.proxy.https port ${port}`,
]
// http
if (config.get().proxy.proxyHttp) {
setProxyCmd.push(`gsettings set org.gnome.system.proxy.http host ${ip}`)
setProxyCmd.push(`gsettings set org.gnome.system.proxy.http port ${port - 1}`)
} else {
setProxyCmd.push('gsettings set org.gnome.system.proxy.http host \'\'')
setProxyCmd.push('gsettings set org.gnome.system.proxy.http port 0')
}
// 设置排除域名(ignore-hosts)
const excludeIpStr = getProxyExcludeIpStr('\', \'')
setProxyCmd.push(`gsettings set org.gnome.system.proxy ignore-hosts "['${excludeIpStr}']"`)
await exec(setProxyCmd)
} else { // 关闭代理
const setProxyCmd = [
'gsettings set org.gnome.system.proxy mode none',
]
await exec(setProxyCmd)
}
},
async mac (exec, params = {}) {
// exec = _exec
let wifiAdaptor = await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 "')
wifiAdaptor = wifiAdaptor.trim()
wifiAdaptor = wifiAdaptor.substring(wifiAdaptor.indexOf(' ')).trim()
const { ip, port } = params
if (ip != null) { // 设置代理
// 延迟加载config
loadConfig()
// https
await exec(`networksetup -setsecurewebproxy "${wifiAdaptor}" ${ip} ${port}`)
// http
if (config.get().proxy.proxyHttp) {
await exec(`networksetup -setwebproxy "${wifiAdaptor}" ${ip} ${port - 1}`)
} else {
await exec(`networksetup -setwebproxystate "${wifiAdaptor}" off`)
}
// 设置排除域名
const excludeIpStr = getProxyExcludeIpStr('" "')
await exec(`networksetup -setproxybypassdomains "${wifiAdaptor}" "${excludeIpStr}"`)
// const setEnv = `cat <> ~/.zshrc
// export http_proxy="http://${ip}:${port}"
// export https_proxy="http://${ip}:${port}"
// ENDOF
// source ~/.zshrc
// `
// await exec(setEnv)
} else { // 关闭代理
// https
await exec(`networksetup -setsecurewebproxystate "${wifiAdaptor}" off`)
// http
await exec(`networksetup -setwebproxystate "${wifiAdaptor}" off`)
// const removeEnv = `
// sed -ie '/export http_proxy/d' ~/.zshrc
// sed -ie '/export https_proxy/d' ~/.zshrc
// source ~/.zshrc
// `
// await exec(removeEnv)
}
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/scripts/set-system-proxy/refresh-internet.js
================================================
const script = `
$signature = @'
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
'@
$INTERNET_OPTION_SETTINGS_CHANGED = 39
$INTERNET_OPTION_REFRESH = 37
$type = Add-Type -MemberDefinition $signature -Name wininet -Namespace pinvoke -PassThru
$a = $type::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
$b = $type::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
$a -and $b
`
module.exports = script
================================================
FILE: packages/core/src/shell/scripts/setup-ca.js
================================================
const Shell = require('../shell')
const execute = Shell.execute
const executor = {
async windows (exec, { certPath }) {
const cmds = [`start "" "${certPath}"`]
await exec(cmds, { type: 'cmd' })
return true
},
async linux (exec, { certPath }) {
const cmds = [`sudo cp ${certPath} /usr/local/share/ca-certificates`, 'sudo update-ca-certificates ']
await exec(cmds)
return true
},
async mac (exec, { certPath }) {
const cmds = [`open "${certPath}"`]
await exec(cmds, { type: 'cmd' })
return true
},
}
module.exports = async function (args) {
return execute(executor, args)
}
================================================
FILE: packages/core/src/shell/shell.js
================================================
const childProcess = require('node:child_process')
const os = require('node:os')
const fixPath = require('fix-path')
const PowerShell = require('node-powershell')
const log = require('../utils/util.log.core')
fixPath()
class SystemShell {
static async exec (cmds, args) {
throw new Error('You have to implement the method exec!')
}
}
class LinuxSystemShell extends SystemShell {
static async exec (cmds) {
if (typeof cmds === 'string') {
cmds = [cmds]
}
for (const cmd of cmds) {
await childExec(cmd, { shell: '/bin/bash' })
}
}
}
class DarwinSystemShell extends SystemShell {
static async exec (cmds) {
if (typeof cmds === 'string') {
cmds = [cmds]
}
let ret
for (const cmd of cmds) {
ret = await childExec(cmd)
}
return ret
}
}
class WindowsSystemShell extends SystemShell {
static async exec (cmds, args = { }) {
let { type } = args
type = type || 'ps'
if (typeof cmds === 'string') {
cmds = [cmds]
}
if (type === 'ps') {
const ps = new PowerShell({
executionPolicy: 'Bypass',
noProfile: true,
})
for (const cmd of cmds) {
ps.addCommand(cmd)
}
try {
return await ps.invoke()
} finally {
ps.dispose()
}
} else {
let compose = 'chcp 65001' // 'chcp 65001 '
for (const cmd of cmds) {
compose += ` && ${cmd}`
}
// compose += '&& exit'
return await childExec(compose, args)
}
}
}
function childExec (composeCmds, options = {}) {
return new Promise((resolve, reject) => {
log.info('shell:', composeCmds)
childProcess.exec(composeCmds, options, (error, stdout, stderr) => {
if (error) {
if (options.printErrorLog !== false) {
log.error('cmd 命令执行错误:\n===>\ncommands:', composeCmds, '\n error:', error, '\n<===')
}
reject(new Error(stderr))
} else {
// log.info('cmd 命令完成:', stdout)
resolve(stdout.replace('Active code page: 65001\r\n', ''))
}
// log.info('关闭 cmd')
// ps.kill('SIGINT')
})
})
}
function getSystemShell () {
switch (getSystemPlatform(true)) {
case 'mac':
return DarwinSystemShell
case 'linux':
return LinuxSystemShell
case 'windows':
return WindowsSystemShell
default:
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
}
}
function getSystemPlatform (throwIfUnknown = false) {
switch (os.platform()) {
case 'darwin':
return 'mac'
case 'linux':
return 'linux'
case 'win32':
return 'windows'
case 'win64':
return 'windows'
default:
log.error(`UNKNOWN OS TYPE: ${os.platform()}`)
if (throwIfUnknown) {
throw new Error(`UNKNOWN OS TYPE '${os.platform()}'`)
} else {
return 'unknown-os'
}
}
}
async function execute (executor, args) {
return executor[getSystemPlatform(true)](getSystemShell().exec, args)
}
async function execFile (file, args, options) {
return new Promise((resolve, reject) => {
try {
childProcess.execFile(file, args, options, (err, stdout) => {
if (err) {
log.error('文件执行出错:', file, err)
reject(err)
return
}
log.debug('文件执行成功:', file)
resolve(stdout)
})
} catch (e) {
log.error('文件执行出错:', file, e)
reject(e)
}
})
}
module.exports = {
getSystemShell,
getSystemPlatform,
execute,
execFile,
}
================================================
FILE: packages/core/src/status.js
================================================
const lodash = require('lodash')
const event = require('./event')
const log = require('./utils/util.log.core')
const status = {
server: { enabled: false },
proxy: {},
plugin: {},
}
event.register('status', (event) => {
lodash.set(status, event.key, event.value)
log.info('status changed:', event)
}, -999)
module.exports = status
================================================
FILE: packages/core/src/utils/util.date.js
================================================
module.exports = {
format (date, needMill = true) {
if (date == null) {
return 'null'
}
const year = date.getFullYear() // 获取年份
const month = (date.getMonth() + 1).toString().padStart(2, '0') // 获取月份(注意月份从 0 开始计数)
const day = date.getDate().toString().padStart(2, '0') // 获取天数
const hours = date.getHours().toString().padStart(2, '0') // 获取小时
const minutes = date.getMinutes().toString().padStart(2, '0') // 获取分钟
const seconds = date.getSeconds().toString().padStart(2, '0') // 获取秒数
const milliseconds = needMill ? `.${date.getMilliseconds().toString().padStart(3, '0')}` : '' // 获取毫秒
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}${milliseconds}`
},
now (needMill = true) {
return this.format(new Date(), needMill)
},
}
================================================
FILE: packages/core/src/utils/util.log-or-console.js
================================================
const dateUtil = require('./util.date')
let log = console
// 将console中的日志缓存起来,当setLogger时,将控制台的日志写入日志文件
let backupLogs = []
function backup (fun, args) {
if (backupLogs === null) {
return
}
try {
backupLogs.push({
fun,
args,
time: dateUtil.format(new Date()),
})
// 最多缓存 100 条
if (backupLogs.length > 100) {
backupLogs = backupLogs.slice(1)
}
} catch {
}
}
function printBackups () {
if (backupLogs === null || log === console) {
return
}
try {
const backups = backupLogs
backupLogs = null // 先置空历史消息对象,再记录日志
for (const item of backups) {
log[item.fun](...[`[${item.time}] console -`, ...item.args])
}
} catch {
}
}
function _doLog (fun, args) {
if (log === console) {
log[fun](...[`[${fun.toUpperCase()}]`, ...args])
backup(fun, args) // 控制台日志备份起来
} else {
log[fun](...args)
}
}
module.exports = {
setLogger (logger) {
if (logger == null) {
log.error('logger 不能为空')
return
}
if (logger === log) {
return
}
log = logger
if (log !== console) {
try {
if (backupLogs && backupLogs.length > 0) {
log.info('[util.log-or-console.js] 日志系统已初始化完成,现开始将历史控制台信息记录到日志文件中:')
printBackups()
}
} catch {
}
}
},
debug (...args) {
_doLog('debug', args)
},
info (...args) {
_doLog('info', args)
},
warn (...args) {
_doLog('warn', args)
},
error (...args) {
_doLog('error', args)
},
}
================================================
FILE: packages/core/src/utils/util.log.core.js
================================================
const loggerFactory = require('./util.logger')
const logger = loggerFactory.getLogger('core')
module.exports = logger
================================================
FILE: packages/core/src/utils/util.logger.js
================================================
const path = require('node:path')
const log4js = require('log4js')
const logOrConsole = require('./util.log-or-console')
const defaultConfig = require('../config/index.js')
const configFromFiles = defaultConfig.configFromFiles
// 日志级别
const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'
function getDefaultConfigBasePath () {
if (configFromFiles.app.logFileSavePath) {
let logFileSavePath = configFromFiles.app.logFileSavePath
if (logFileSavePath.endsWith('/') || logFileSavePath.endsWith('\\')) {
logFileSavePath = logFileSavePath.slice(0, -1)
}
// eslint-disable-next-line no-template-curly-in-string
return logFileSavePath.replace('${userBasePath}', configFromFiles.server.setting.userBasePath)
} else {
return path.join(configFromFiles.server.setting.userBasePath, '/logs')
}
}
// 日志文件目录
const basePath = getDefaultConfigBasePath()
// 通用日志配置
const appenderConfig = {
type: 'file',
pattern: 'yyyy-MM-dd',
compress: true, // 压缩日志文件
keepFileExt: true, // 保留日志文件扩展名为 .log
backups: Math.ceil(configFromFiles.app.keepLogFileCount) || defaultConfig.app.keepLogFileCount, // 保留日志文件数
maxLogSize: Math.ceil((configFromFiles.app.maxLogFileSize || defaultConfig.app.maxLogFileSize) * 1024 * 1024 * (configFromFiles.app.maxLogFileSizeUnit === 'GB' ? 1024 : 1)), // 目前单位只有GB和MB
}
let log = null
// 设置一组日志配置
function log4jsConfigure (categories) {
if (log != null) {
log.error('当前进程已经设置过日志配置,无法再设置更多日志配置:', categories)
return
}
const config = {
appenders: {
std: { type: 'stdout' },
},
categories: {
default: { appenders: ['std'], level },
},
}
for (const category of categories) {
config.appenders[category] = { ...appenderConfig, filename: path.join(basePath, `/${category}.log`) }
config.categories[category] = { appenders: [category, 'std'], level }
}
log4js.configure(config)
// 拿第一个日志类型来logger并设置到log变量中
log = log4js.getLogger(categories[0])
logOrConsole.setLogger(log)
log.info(`设置日志配置完成,进程ID: ${process.pid},categories:[${categories}],config:`, JSON.stringify(config))
}
module.exports = {
getLogger (category) {
if (!category) {
if (log) {
log.error('未指定日志类型,无法配置并获取日志对象!!!')
}
throw new Error('未指定日志类型,无法配置并获取日志对象!!!')
}
if (category === 'core' || category === 'gui') {
// core 和 gui 的日志配置,因为它们在同一进程中,所以一起配置,且只能配置一次
if (log == null) {
log4jsConfigure(['core', 'gui'])
}
return log4js.getLogger(category)
} else {
if (log == null) {
log4jsConfigure([category])
} else if (category !== log.category) {
log.error(`当前进程已经设置过日志配置,无法再设置 "${category}" 的配置,先临时返回 "${log.category}" 的 log 进行日志记录。如果与其他类型的日志在同一进程中写入,请参照 core 和 gui 一起配置`)
}
return log
}
},
}
================================================
FILE: packages/core/src/utils/util.version.js
================================================
function parseVersion (version) {
const matched = version.match(/^v?(\d{1,2}(?:\.\d{1,2})*)(.*)$/)
return {
versions: matched[1].split('.'), // 版本号数组
pre: matched[2], // 预发布版本号
}
}
/**
* 比较版本号
*
* @param onlineVersion 线上版本号
* @param currentVersion 当前版本号
* @param log 日志对象
* @returns {number} 比较线上版本号是否为更新的版本,大于0=是|0=相等|小于0=否|-999=出现异常,比较结果未知
*/
export function isNewVersion (onlineVersion, currentVersion, log = null) {
if (onlineVersion === currentVersion) {
return 0
}
try {
const onlineVersionObj = parseVersion(onlineVersion)
const curVersionObj = parseVersion(currentVersion)
const { versions: versions1 } = onlineVersionObj
const { versions: versions2 } = curVersionObj
if (versions1.length !== versions2.length) {
// 短的数组补0
if (versions1.length < versions2.length) {
for (let i = versions1.length; i < versions2.length; i++) {
versions1.push('0')
}
} else if (versions1.length > versions2.length) {
for (let i = versions2.length; i < versions1.length; i++) {
versions2.push('0')
}
}
}
// 版本数组比对
for (let i = 0; i < versions1.length; i++) {
if (versions1[i] > versions2[i]) {
return i + 1 // 为新版本,需要更新
} else if (versions1[i] < versions2[i]) {
return -(i + 1) // 为旧版本,无需更新
}
}
// 版本号相同,继续比对预发布版本号
if (onlineVersionObj.pre && curVersionObj.pre) {
// 都为预发布版本时,直接比较预发布版本号字符串的大小
if (onlineVersionObj.pre > curVersionObj.pre) {
return 101
} else if (onlineVersionObj.pre < curVersionObj.pre) {
return -101
}
} else if (!onlineVersionObj.pre && curVersionObj.pre) {
// 线上为正式版本,当前版本为预发布版本,需要更新
return 102
} else if (onlineVersionObj.pre && !curVersionObj.pre) {
// 线上为预发布版本,当前版本为正式版本,无需更新
return -102
}
return 0 // 相同版本,无需更新
} catch (e) {
(log || console).error(`比对版本失败,当前版本号:${currentVersion},线上版本号:${onlineVersion}, error:`, e)
return -999 // 比对异常
}
}
================================================
FILE: packages/core/test/configTest.js
================================================
// const config = require('../src/config-api')
//
// config.set({
// server: {
// intercepts: {
// 'github1.githubassets.com': {
// '.*': {
// redirect: 'assets.fastgit.org',
// test: 'https://github.githubassets.com/favicons/favicon.svg',
// desc: '静态资源加速'
// }
// },
// 'github.githubassets.com': null
// }
// }
// })
//
// console.log(config.get())
//
// config.reload()
================================================
FILE: packages/core/test/httpsVerifyTest.js
================================================
// const https = require('node:https')
//
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'
//
// function request () {
// return new Promise((resolve, reject) => {
// const options = {
// hostname: 'test1.gagedigital.com',
// port: 443,
// path: '/ssltest.php',
// method: 'GET',
// rejectUnauthorized: true,
// }
// console.log('ssl test: gagedigital')
// const req = https.request(options, (res) => {
// console.log('statusCode:', res.statusCode)
// console.log('headers:', res.headers)
//
// res.on('data', (d) => {
// process.stdout.write(d)
// resolve()
// })
// })
//
// req.on('error', (e) => {
// console.error(e)
// reject(e)
// })
// req.end()
// })
// }
// // eslint-disable-next-line no-undef
// describe('ssl.verify', () => {
// // eslint-disable-next-line no-undef
// it('regex.test.js', async () => {
// // https.request('https://test1.gagedigital.com/ssltest.php')
// await request()
//
// // expect(ret).be.ok
// })
// })
================================================
FILE: packages/core/test/macProxyTest.js
================================================
const assert = require('node:assert')
// const childProcess = require('child_process')
// const util = require('util')
// const exec = util.promisify(childProcess.exec)
//
// async function test () {
// const wifiAdaptor = (await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 | cut -d \' \' -f2"')).stdout.trim()
//
// await exec(`networksetup -setwebproxystate '${wifiAdaptor}' off`)
// return await exec(`networksetup -setsecurewebproxystate '${wifiAdaptor}' off`)
// }
// test().then((ret) => {
// console.log('haha', ret)
// })
let wifiAdaptor = '(151) test'
wifiAdaptor = wifiAdaptor.substring(wifiAdaptor.indexOf(' ')).trim()
console.log(wifiAdaptor)
assert.strictEqual(wifiAdaptor, 'test')
================================================
FILE: packages/core/test/mergeTest.js
================================================
const assert = require('node:assert')
const lodash = require('lodash')
const mergeApi = require('../src/merge.js')
// 默认配置
const defConfig = {
a: {
aa: { value: 1 },
bb: { value: 2 },
},
b: { c: 2 },
c: 1,
d: [1, 2, 3],
e: {
aa: 2,
ee: 5,
},
f: {
x: 1,
},
g: [1, 2],
h: null,
i: null,
}
// 自定义配置
const customConfig = {
a: {
bb: { value: 2 },
cc: { value: 3 },
},
b: { c: 2 },
c: null,
d: [1, 2, 3, 4],
e: {
aa: 2,
ee: 5,
ff: 6,
},
f: {},
g: [1, 2],
h: null,
}
// doDiff
const doDiffResult = mergeApi.doDiff(defConfig, customConfig)
console.log('doDiffResult:', JSON.stringify(doDiffResult, null, 2))
console.log('\r')
// 校验doDiff结果
const doDiffExpect = {
a: {
aa: null,
cc: { value: 3 },
},
c: null,
d: [1, 2, 3, 4],
e: {
ff: 6,
},
f: {
x: null,
},
}
console.log('check diff result:', lodash.isEqual(doDiffResult, doDiffExpect))
console.log('\r')
// doMerge
const doMergeResult = mergeApi.doMerge(defConfig, doDiffResult)
// delete null item
mergeApi.deleteNullItems(doMergeResult)
console.log('running:', JSON.stringify(doMergeResult, null, 2))
// 校验doMerge结果
const doMergeExpect = {
a: {
bb: { value: 2 },
cc: { value: 3 },
},
b: { c: 2 },
d: [1, 2, 3, 4],
e: {
aa: 2,
ee: 5,
ff: 6,
},
f: {},
g: [1, 2],
}
const result = lodash.isEqual(doMergeResult, doMergeExpect)
console.log('check merge result:', result)
console.log('\r')
assert.strictEqual(result, true)
================================================
FILE: packages/core/test/regex.test.js
================================================
const assert = require('node:assert')
const expect = require('chai').expect
// eslint-disable-next-line no-undef
describe('test', () => {
// eslint-disable-next-line no-undef
it('regexp', () => {
const test = '^/[^/]+/[^/]+(?:/releases(?:/.*)?)?$'
const reg = new RegExp(test)
const ret = reg.test('/docmirror/dev-sidecar/releases/tag')
console.log(ret)
assert.strictEqual(ret, true)
expect(ret).be.ok
})
})
================================================
FILE: packages/core/test/requestTest.js
================================================
const HttpsAgent = require('@docmirror/mitmproxy/src/lib/proxy/common/ProxyHttpsAgent')
const request = require('request')
const options = {
url: 'https://raw.githubusercontent.com/docmirror/dev-sidecar/refs/heads/master/packages/core/src/config/remote_config.json5',
// url: 'https://gitee.com/wangliang181230/dev-sidecar/raw/docmirror2.x/packages/core/src/config/remote_config.json',
servername: 'baidu.com',
agent: new HttpsAgent({
keepAlive: true,
timeout: 20000,
keepAliveTimeout: 30000,
rejectUnauthorized: false,
}),
}
if (options.agent.options) {
options.agent.options.rejectUnauthorized = false
console.info('options.agent.options.rejectUnauthorized = false')
}
request(options, (error, response, body) => {
console.info('error:', error, '\n---------------------------------------------------------------------------\n'
+ 'response:', response, '\n---------------------------------------------------------------------------\n'
+ 'body:', body)
})
================================================
FILE: packages/core/test/versionTest.js
================================================
const assert = require('node:assert')
const { isNewVersion } = require('../src/utils/util.version.js')
function testIsNewVersion (onlineVersion, currentVersion, expected) {
const ret = isNewVersion(onlineVersion, currentVersion)
console.log(ret >= 0 ? ` ${ret}` : `${ret}`)
assert.strictEqual(ret, expected)
}
testIsNewVersion('2.0.0', '2.0.0', 0)
testIsNewVersion('2.0.0', '1.0.0', 1)
testIsNewVersion('1.0.0', '2.0.0', -1)
testIsNewVersion('2.1.0', '2.0.0', 2)
testIsNewVersion('2.0.0', '2.1.0', -2)
testIsNewVersion('2.0.1', '2.0.0', 3)
testIsNewVersion('2.0.0', '2.0.1', -3)
testIsNewVersion('2.0.0.1', '2.0.0', 4)
testIsNewVersion('2.0.0', '2.0.0.1', -4)
testIsNewVersion('2.0.0.9.1', '2.0.0.9', 5)
testIsNewVersion('2.0.0.9', '2.0.0.9.1', -5)
testIsNewVersion('2.0.0-RC2', '2.0.0-RC1', 101)
testIsNewVersion('2.0.0-RC1', '2.0.0-RC2', -101)
testIsNewVersion('2.0.0', '2.0.0-RC1', 102)
testIsNewVersion('2.0.0-RC1', '2.0.0', -102)
testIsNewVersion('2.0.0.0', '2.0.0', 0)
testIsNewVersion('x', 'v', -999)
================================================
FILE: packages/gui/.editorconfig
================================================
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: packages/gui/.gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.lock
*.log
#Electron-builder output
/dist_electron
/config
================================================
FILE: packages/gui/LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: packages/gui/README.md
================================================
# dev-sidecar-gui
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
================================================
FILE: packages/gui/babel.config.js
================================================
module.exports = {
presets: [
'@vue/babel-preset-jsx',
],
}
================================================
FILE: packages/gui/extra/pac/pac.txt
================================================
[AutoProxy 0.2.9]
! Checksum: BZUefB22itmhAjqqdpvRkA
! Expires: 6h
! Title: GFWList4LL
! GFWList with EVERYTHING included
! Last Modified: Sun, 12 Jan 2025 11:56:36 -0500
!
! HomePage: https://github.com/gfwlist/gfwlist
! License: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
!
! GFWList is unlikely to fully comprise the real
! rules being deployed inside GFW system. We try
! our best to keep the list up to date. Please
! contact us regarding URL submission / removal,
! or suggestion / enhancement at issue tracker:
! https://github.com/gfwlist/gfwlist/issues/.
!---------403/451/503/520 & URL Redirects---------
!--ehentai
|http://85.17.73.31/
!--||adorama.com
||afreecatv.com
||agnesb.fr
||akiba-web.com
||altrec.com
||angela-merkel.de
||angola.org
||anthropic.com
||apartmentratings.com
||apartments.com
||arena.taipei
||asianspiss.com
||assimp.org
||athenaeizou.com
||azubu.tv
||bankmobilevibe.com
||banorte.com
||beeg.com
||global.bing.com
||booktopia.com.au
||boysmaster.com
||bynet.co.il
||byrut.org
||carfax.com
.casinobellini.com
||casinobellini.com
||centauro.com.br
||chobit.cc
||ciciai.com
||claude.ai
||clearsurance.com
||cnbeta.com.tw
||images.comico.tw
||static.comico.tw
||counter.social
||costco.com
||coze.com
||crossfire.co.kr
||crunchyroll.com
||d2pass.com
||darpa.mil
||dawangidc.com
||deezer.com
||desipro.de
||dingchin.com.tw
||discord.com
||discord.gg
||discordapp.com
||discordapp.net
||dish.com
|http://img.dlsite.jp/
||dm530.net
share.dmhy.org
||dmhy.org
||dmm.co.jp
|http://www.dmm.com/netgame
||dnvod.tv
||dubox.com
||dvdpac.com
||eesti.ee
||esurance.com
.expekt.com
||expekt.com
.extmatrix.com
||extmatrix.com
||fakku.net
||fastpic.ru
||filesor.com
||financetwitter.com
||flipboard.com
||flitto.com
||fnac.be
||fnac.com
||funkyimg.com
||fxnetworks.com
||g-area.org
||gettyimages.com
||getuploader.com
||ghidra-sre.org
!--|https://github.com/programthink/zhao
!--|https://raw.githubusercontent.com/programthink/zhao
||glass8.eu
||glype.com
||go141.com
||guo.media
||hautelook.com
||hautelookcdn.com
||wego.here.com
||gamer-cds.cdn.hinet.net
||gamer2-cds.cdn.hinet.net
||hmoegirl.com
||hmvdigital.ca
||hmvdigital.com
||homedepot.com
||hoovers.com
||hulu.com
||huluim.com
|http://secure.hustler.com
|http://hustlercash.com
|http://www.hustlercash.com
||hybrid-analysis.com
||cdn*.i-scmp.com
||ilbe.com
||ilovelongtoes.com
|http://imgmega.com/*.gif.html
|http://imgmega.com/*.jpg.html
|http://imgmega.com/*.jpeg.html
|http://imgmega.com/*.png.html
||imlive.com
||tw.iqiyi.com
||javhub.net
||javhuge.com
.javlibrary.com
||javlibrary.com
||jcpenney.com
||jims.net
||tv.jtbc.joins.com
||jukujo-club.com
||juliepost.com
||kawaiikawaii.jp
||kendatire.com
||khatrimaza.org
||kkbox.com
||leisurepro.com
||lifemiles.com
||longtoes.com
||lovetvshow.com
|http://www.m-sport.co.uk
||macgamestore.com
||madonna-av.com
||mandiant.com
||mangafox.com
||mangafox.me
||manta.com
||matome-plus.com
||matome-plus.net
||mattwilcox.net
||metarthunter.com
||mfxmedia.com
||miraheze.org
||mojim.com
||kb.monitorware.com
||monster.com
||moodyz.com
||moonbingo.com
||mos.ru
||msha.gov
||muzu.tv
||mvg.jp
.mybet.com
||mybet.com
||mypikpak.com
||nationwide.com
|http://www.nbc.com/live
||neo-miracle.com
||netflix.com
||netflix.net
||nflximg.com
||nflximg.net
||nflxext.com
||nflxso.net
||nflxvideo.net
||nic.gov
|http://mo.nightlife141.com
||purpose.nike.com
||noxinfluencer.com
@@||cn.noxinfluencer.com
||nordstrom.com
||nordstromimage.com
||nordstromrack.com
||nottinghampost.com
||npsboost.com
||ntdtv.cz
||s1.nudezz.com
||nusatrip.com
||nuuvem.com
||olehdtv.com
||omni7.jp
||onapp.com
!--We are confused as well
||ontrac.com
@@|http://blog.ontrac.com
||openai.com
||pandora.com
.pandora.tv
||parkansky.com
||phmsociety.org
|http://*.pimg.tw/
||podcast.co
||pure18.com
||pytorch.org
||qq.co.za
||r18.com
|http://radiko.jp
||ramcity.com.au
||rateyourmusic.com
||rd.com
||rdio.com
|https://riseup.net
||sadistic-v.com
||isc.sans.edu
|http://cdn*.search.xxx/
||shiksha.com
||slacker.com
||sm-miracle.com
||softnology.biz
||soylentnews.org
||spotify.com
||spreadshirt.es
||springboardplatform.com
||sprite.org
@@|http://store.sprite.org
||superokayama.com
||superpages.com
||swagbucks.com
||switch1.jp
||tapanwap.com
||gsp.target.com
||login.target.com
!--@@||intl.target.com
||rcam.target.com
||technews.tw
||terabox.com
||thinkgeek.com
||thebodyshop-usa.com
||tma.co.jp
||tracfone.com
||tryheart.jp
||turntable.fm
||twerkingbutt.com
||ulop.net
||uukanshu.com
||vegasred.com
||vevo.com
||vip-enterprise.com
|http://viu.tv/ch/
|http://viu.tv/encore/
||vmpsoft.com
|http://ecsm.vs.com/
||wanz-factory.com
||ssl.webpack.de
||wheretowatch.com
||wingamestore.com
||wizcrafts.net
||wowhead.com
||vod.wwe.com
||xfinity.com
||xiaomi.eu
||youwin.com
||ytn.co.kr
||zamimg.com
||zattoo.com
||zim.vn
||zozotown.com
!##############General List Start###############
!-------------------Pure IP---------------------
14.102.250.18
14.102.250.19
50.7.31.230:8898
174.142.105.153
69.65.19.160
!----------------------IDN----------------------
||xn--4gq171p.com
||xn--czq75pvv1aj5c.org
||xn--i2ru8q2qg.com
||xn--oiq.cc
||xn--p8j9a0d9c9a.xn--q9jyb4c
||xn--9pr62r24a.com
!-----------------DNS Poisoning-----------------
!---Amazon---
!-||cdn-images.mailchimp.com
||abebooks.com
|https://*.s3.amazonaws.com
||s3-ap-southeast-2.amazonaws.com
||43110.cf
||9cache.com
||9gag.com
||agro.hk
||share.america.gov
||apkmirror.com
||arte.tv
||artstation.com
||bangdream.space
||behance.net
||bird.so
||bitterwinter.org
||bnn.co
||businessinsider.com
||boomssr.com
||bwgyhw.com
||castbox.fm
||chinatimes.com
||clyp.it
||cmcn.org
||cmx.im
||dailyview.tw
||daum.net
||depositphotos.com
||disconnect.me
||documentingreality.com
||doubibackup.com
||doubmirror.cf
||encyclopedia.com
||fangeqiang.com
||fanqiangdang.com
||feedly.com
||feedx.net
||flyzy2005.com
||foreignpolicy.com
||free-ss.site
||freehongkong.org
||blog.fuckgfw233.org
||g0v.social
||globalvoices.org
||glorystar.me
||goregrish.com
||guangnianvpn.com
||hanime.tv
||hbo.com
||spaces.hightail.com
||hkgalden.com
||hkgolden.com
||hudson.org
||ipfs.io
||japantimes.co.jp
||jiji.com
||jintian.net
||jinx.com
||joinmastodon.org
||liangzhichuanmei.com
||lighti.me
||lightyearvpn.com
||lihkg.com
||line-scdn.net
||i.lithium.com
||cloud.mail.ru
||cdn-images.mailchimp.com
||mastodon.cloud
||mastodon.host
||mastodon.social
||mastodon.xyz
||matters.news
||me.me
||metart.com
||mohu.club
||mohu.ml
||motiyun.com
||msa-it.org
||goo.ne.jp
||go.nesnode.com
||international-news.newsmagazine.asia
||nikkei.com
||nitter.cc
||nitter.net
||niu.moe
||nofile.io
||now.com
||openvpn.org
||onejav.com
||paste.ee
||my.pcloud.com
||picacomic.com
||pincong.rocks
||pixiv.net
||potato.im
||premproxy.com
||prism-break.org
||proton.me
||protonvpn.com
||api.pureapk.com
||quora.com
||quoracdn.net
||qz.com
||cdn.seatguru.com
||secure.raxcdn.com
||redd.it
||reddit.com
.redditlist.com
|http://redditlist.com
||redditmedia.com
||redditstatic.com
!--defunct
||rixcloud.com
||rixcloud.us
||rsdlmonitor.com
||shadowsocks.be
||shadowsocks9.com
||tn1.shemalez.com
||tn2.shemalez.com
||tn3.shemalez.com
||static.shemalez.com
||six-degrees.io
||softfamous.com
||softsmirror.cf
||sosreader.com
||sspanel.net
||sulian.me
||supchina.com
||teddysun.com
||textnow.me
||tineye.com
||top10vpn.com
||tubepornclassic.com
||uku.im
||unseen.is
||cn.uptodown.com
||uraban.me
||vrsmash.com
||vultryhw.com
||scache.vzw.com
||scache1.vzw.com
||scache2.vzw.com
||ss7.vzw.com
||ssr.tools
||steemit.com
||taiwanjustice.net
||tinc-vpn.org
||u15.info
||washingtonpost.com
||wenzhao.ca
||whatsonweibo.com
||wire.com
||blog.workflow.is
||xm.com
||xuehua.us
||yes-news.com
||yigeni.com
||you-get.org
||zzcloud.me
!---Digital Currency Exchange(CRYPTO)---
||aex.com
||allcoin.com
||adcex.com
||bcex.ca
||bibox.com
||big.one
||bigone.com
||binance.com
||bit-z.com
||bitz.ai
||bitbay.net
||bitcoinworld.com
||bitfinex.com
||bithumb.com
||bitinka.com.ar
||bitmex.com
||bnbstatic.com
||btc98.com
||btcbank.bank
||btctrade.im
||bybit.com
||c2cx.com
||chaoex.com
||cobinhood.com
||coin2co.in
||coinbene.com
.coinegg.com
||coinegg.com
||coinex.com
!--|https://www.coinexchange.io/
||coingecko.com
||coingi.com
||coinmarketcap.com
||coinrail.co.kr
||cointiger.com
||cointobe.com
||coinut.com
||discoins.com
||dragonex.io
||ebtcbank.com
||etherdelta.com
||ethermine.org
||etherscan.io
||exmo.com
||exrates.me
||exx.com
||f2pool.com
||fatbtc.com
||ftx.com
||gate.io
||gatecoin.com
||hbg.com
||hitbtc.com
||hotcoin.com
||huobi.co
||huobi.com
||huobi.me
!--||huobi.li
||huobi.pro
||huobi.sc
||huobipro.com
||bx.in.th
||jex.com
||kex.com
||kraken.com
||kspcoin.com
||kucoin.com
||lbank.info
||liquiditytp.com
||livecoin.net
||localbitcoins.com
||mercatox.com
||oanda.com
||obyte.org
||oex.com
||okex.com
||okx.com
||opensea.io
||otcbtc.com
||paxful.com
||poolin.com
||rightbtc.com
||solv.finance
||topbtc.com
||tronscan.org
||xbtce.com
||yobit.net
||zb.com
!----------------Frauds & Scams-----------------
!!---Content Farm(fake 500 error)---
||read01.com
||kknews.cc
china-mmm.jp.net
.lsxszzg.com
.china-mmm.net
||china-mmm.net
china-mmm.sa.com
!---------------------Groups--------------------
!!---Afraid FreeDNS---
.allowed.org
.now.im
!!---Amazon---
||amazon.co.jp
.amazon.com/Dalai-Lama
amazon.com/Prisoner-State-Secret-Journal-Premier
s3-ap-northeast-1.amazonaws.com
!!---AOL---
||aolchannels.aol.com
video.aol.ca/video-detail
video.aol.co.uk/video-detail
video.aol.com
||video.aol.com
||search.aol.com
www.aolnews.com
!!---AvMoo---
.avmo.pw
!--|http://avmo.pw
.avmoo.com
|http://avmoo.com
.avmoo.net
|http://avmoo.net
||avmoo.pw
.javmoo.xyz
|http://javmoo.xyz
.javtag.com
|http://javtag.com
.javzoo.com
|http://javzoo.com
.tellme.pw
!!---BBC---
!--.bbc.co.uk/blogs
!--.bbc.co.uk/chinese
!--.bbc.co.uk/news/world-asia-china
!--.bbc.co.uk/tv
!--.bbc.co.uk/zhongwen
!--.bbc.com/ukchina
!--.bbc.com/zhongwen
!--.bbc.com%2Fzhongwen
!--news.bbc.co.uk/onthisday*newsid_2496000/2496277
!--newsforums.bbc.co.uk
.bbc.com
||bbc.com
.bbc.co.uk
||bbc.co.uk
||bbci.co.uk
.bbcchinese.com
||bbcchinese.com
|http://bbc.in
!!---Bloomberg---
.bloomberg.cn
||bloomberg.cn
.bloomberg.com
||bloomberg.com
bloomberg.de
||bloomberg.de
||bloombergview.com
.businessweek.com
!!---ChangeIP---
.1dumb.com
.25u.com
.2waky.com
.3-a.net
.4dq.com
.4mydomain.com
.4pu.com
.acmetoy.com
.almostmy.com
.americanunfinished.com
.authorizeddns.net
.authorizeddns.org
.authorizeddns.us
.bigmoney.biz
.changeip.name
.changeip.net
.changeip.org
.cleansite.biz
.cleansite.info
.cleansite.us
.compress.to
.ddns.info
.ddns.me.uk
.ddns.mobi
.ddns.ms
.ddns.name
.ddns.us
.dhcp.biz
.dns-dns.com
.dns-stuff.com
.dns04.com
.dns05.com
.dns1.us
.dns2.us
.dnset.com
.dnsrd.com
.dsmtp.com
.dumb1.com
.dynamic-dns.net
.dynamicdns.biz
.dynamicdns.co.uk
.dynamicdns.me.uk
.dynamicdns.org.uk
.dyndns.pro
.dynssl.com
.edns.biz
.epac.to
.esmtp.biz
.ezua.com
.faqserv.com
.fartit.com
.freeddns.com
.freetcp.com
.freewww.biz
.freewww.info
.ftp1.biz
.ftpserver.biz
.gettrials.com
.got-game.org
.gr8domain.biz
.gr8name.biz
.https443.net
.https443.org
.ikwb.com
.instanthq.com
.iownyour.biz
.iownyour.org
.isasecret.com
.itemdb.com
.itsaol.com
.jetos.com
.jkub.com
.jungleheart.com
.justdied.com
.lflink.com
.lflinkup.com
.lflinkup.net
.lflinkup.org
.longmusic.com
.mefound.com
.moneyhome.biz
.mrbasic.com
.mrbonus.com
.mrface.com
.mrslove.com
.my03.com
.mydad.info
.myddns.com
.myftp.info
.myftp.name
.mylftv.com
.mymom.info
.mynetav.net
.mynetav.org
.mynumber.org
.mypicture.info
.mypop3.net
.mypop3.org
.mysecondarydns.com
.mywww.biz
.myz.info
.ninth.biz
.ns01.biz
.ns01.info
.ns01.us
.ns02.biz
.ns02.info
.ns02.us
.ns1.name
.ns2.name
.ns3.name
.ocry.com
.onedumb.com
.onmypc.biz
.onmypc.info
.onmypc.net
.onmypc.org
.onmypc.us
.organiccrap.com
.otzo.com
.ourhobby.com
.pcanywhere.net
.port25.biz
.proxydns.com
.qhigh.com
.qpoe.com
.rebatesrule.net
.sellclassics.com
.sendsmtp.com
.serveuser.com
.serveusers.com
.sexidude.com
.sexxxy.biz
.sixth.biz
.squirly.info
.ssl443.org
.toh.info
.toythieves.com
.trickip.net
.trickip.org
.vizvaz.com
.wha.la
.wikaba.com
.www1.biz
.wwwhost.biz
@@|http://xx.wwwhost.biz
.x24hr.com
.xxuz.com
.xxxy.biz
.xxxy.info
.ygto.com
.youdontcare.com
.yourtrap.com
.zyns.com
.zzux.com
!!--Cloudflare--
!--||pages.dev
!!---CloudFront---
d1b183sg0nvnuh.cloudfront.net
|https://d1b183sg0nvnuh.cloudfront.net
d1c37gjwa26taa.cloudfront.net
|https://d1c37gjwa26taa.cloudfront.net
d3c33hcgiwev3.cloudfront.net
|https://d3c33hcgiwev3.cloudfront.net
||d3rhr7kgmtrq1v.cloudfront.net
!!---DtDNS---
!###https://www.dtdns.com/dtsite/faq
.3d-game.com
.4irc.com
.b0ne.com
.chatnook.com
.darktech.org
.deaftone.com
.dtdns.net
.effers.com
.etowns.net
.etowns.org
.flnet.org
.gotgeeks.com
.scieron.com
.slyip.com
.slyip.net
.suroot.com
!!---DynDNS---
!###https://help.dyn.com/list-of-dyn-dns-pro-remote-access-domain-names/
.blogdns.org
.dyndns.org
.dyndns-ip.com
.dyndns-pics.com
.from-sd.com
.from-pr.com
.is-a-hunter.com
!!---Dynu---
.dynu.com
||dynu.com
.dynu.net
.freeddns.org
!!---Facebook---
||accountkit.com
cdninstagram.com
||cdninstagram.com
||f8.com
||facebook.br
.facebook.com
||facebook.com
!--/^https?:\/\/[^\/]+facebook\.com/
@@||v6.facebook.com
||facebook.de
||facebook.design
||connect.facebook.net
||facebook.hu
||facebook.in
||facebook.nl
||facebook.se
||facebookmail.com
||fb.com
||fb.me
||fb.watch
||fbcdn.net
||fbsbx.com
||fbaddins.com
||fbworkmail.com
.instagram.com
||instagram.com
||m.me
||messenger.com
||meta.com
||oculus.com
||oculuscdn.com
||rocksdb.org
@@||ip6.static.sl-reverse.com
||parse.com
||thefacebook.com
||threads.net
||whatsapp.com
||whatsapp.net
!!---Fandom---
||auntology.fandom.com
||hongkong.fandom.com
!!---FTChinese---
.ftchinese.com
||ftchinese.com
!--.ftchinese.com/channel/video
!--.ftchinese.com/premium/001081066
!--.ftchinese.com/story/00102753
!--.ftchinese.com/story/001026616
!--.ftchinese.com/story/001026749
!--.ftchinese.com/story/001026807
!--.ftchinese.com/story/001026808
!--.ftchinese.com/story/001026834
!--.ftchinese.com/story/001026880
!--.ftchinese.com/story/001027429
!--.ftchinese.com/story/001030341
!--.ftchinese.com/story/001030502
!--.ftchinese.com/story/001030803
!--.ftchinese.com/story/001031317
!--.ftchinese.com/story/001032617
!--.ftchinese.com/story/001032636
!--.ftchinese.com/story/001032692
!--.ftchinese.com/story/001032762
!--.ftchinese.com/story/001033138
!--.ftchinese.com/story/001034917
!--.ftchinese.com/story/001034926
!--.ftchinese.com/story/001034927
!--.ftchinese.com/story/001034928
!--.ftchinese.com/story/001034952
!--.ftchinese.com/story/001035890
!--.ftchinese.com/story/001035972
!--.ftchinese.com/story/001035993
!--.ftchinese.com/story/001036417
!--.ftchinese.com/story/001037090
!--.ftchinese.com/story/001037091
!--.ftchinese.com/story/001038178
!--.ftchinese.com/story/001038199
!--.ftchinese.com/story/001038220
!--.ftchinese.com/story/001038819
!--.ftchinese.com/story/001038862
!--.ftchinese.com/story/001039067
!--.ftchinese.com/story/001039178
!--.ftchinese.com/story/001039211
!--.ftchinese.com/story/001039271
!--.ftchinese.com/story/001039295
!--.ftchinese.com/story/001039369
!--.ftchinese.com/story/001039482
!--.ftchinese.com/story/001039534
!--.ftchinese.com/story/001039555
!--.ftchinese.com/story/001039576
!--.ftchinese.com/story/001039712
!--.ftchinese.com/story/001039779
!--.ftchinese.com/story/001039809
!--.ftchinese.com/story/001040134
!--.ftchinese.com/story/001040835
!--.ftchinese.com/story/001040890
!--.ftchinese.com/story/001040918
!--.ftchinese.com/story/001040992
!--.ftchinese.com/story/001041209
!--.ftchinese.com/story/001042100
!--.ftchinese.com/story/001042252
!--.ftchinese.com/story/001042272
!--.ftchinese.com/story/001042280
!--.ftchinese.com/story/001043029
!--.ftchinese.com/story/001043066
!--.ftchinese.com/story/001043096
!--.ftchinese.com/story/001043124
!--.ftchinese.com/story/001043152
!--.ftchinese.com/story/001043189
!--.ftchinese.com/story/001043428
!--.ftchinese.com/story/001043439
!--.ftchinese.com/story/001043534
!--.ftchinese.com/story/001043675
!--.ftchinese.com/story/001043680
!--.ftchinese.com/story/001043702
!--.ftchinese.com/story/001043849
!--.ftchinese.com/story/001044099
!--.ftchinese.com/story/001044776
!--.ftchinese.com/story/001044871
!--.ftchinese.com/story/001044897
!--.ftchinese.com/story/001045114
!--.ftchinese.com/story/001045139
!--.ftchinese.com/story/001045186
!--.ftchinese.com/story/001045755
!--.ftchinese.com/story/001046087
!--.ftchinese.com/story/001046105
!--.ftchinese.com/story/001046118
!--.ftchinese.com/story/001046132
!--.ftchinese.com/story/001046517
!--.ftchinese.com/story/001046822
!--.ftchinese.com/story/001046866
!--.ftchinese.com/story/001046942
!--.ftchinese.com/story/001047180
!--.ftchinese.com/story/001047206
!--.ftchinese.com/story/001047304
!--.ftchinese.com/story/001047317
!--.ftchinese.com/story/001047345
!--.ftchinese.com/story/001047358
!--.ftchinese.com/story/001047375
!--.ftchinese.com/story/001047381
!--.ftchinese.com/story/001047413
!--.ftchinese.com/story/001047456
!--.ftchinese.com/story/001047491
!--.ftchinese.com/story/001047545
!--.ftchinese.com/story/001047558
!--.ftchinese.com/story/001047568
!--.ftchinese.com/story/001047627
!--.ftchinese.com/story/001048293
!--.ftchinese.com/story/001048343
!--.ftchinese.com/story/001048710
!--.ftchinese.com/story/001049289
!--.ftchinese.com/story/001049360
!--.ftchinese.com/story/001049896
!--.ftchinese.com/story/001050152
!--.ftchinese.com/story/001051027
!--.ftchinese.com/story/001051161
!--.ftchinese.com/story/001051372
!--.ftchinese.com/story/001051479
!--.ftchinese.com/story/001052138
!--.ftchinese.com/story/001052161
!--.ftchinese.com/story/001052525
!--.ftchinese.com/story/001052549
!--.ftchinese.com/story/001052701
!--.ftchinese.com/story/001052965
!--.ftchinese.com/story/001053149
!--.ftchinese.com/story/001053150
!--.ftchinese.com/story/001053200
!--.ftchinese.com/story/001053425
!--.ftchinese.com/story/001053496
!--.ftchinese.com/story/001053526
!--.ftchinese.com/story/001053557
!--.ftchinese.com/story/001053906
!--.ftchinese.com/story/001054049
!--.ftchinese.com/story/001054103
!--.ftchinese.com/story/001054109
!--.ftchinese.com/story/001054119
!--.ftchinese.com/story/001054123
!--.ftchinese.com/story/001054139
!--.ftchinese.com/story/001054166
!--.ftchinese.com/story/001054168
!--.ftchinese.com/story/001054190
!--.ftchinese.com/story/001054437
!--.ftchinese.com/story/001054526
!--.ftchinese.com/story/001054607
!--.ftchinese.com/story/001054644
!--.ftchinese.com/story/001054786
!--.ftchinese.com/story/001054843
!--.ftchinese.com/story/001054925
!--.ftchinese.com/story/001054940
!--.ftchinese.com/story/001055051
!--.ftchinese.com/story/001055063
!--.ftchinese.com/story/001055069
!--.ftchinese.com/story/001055136
!--.ftchinese.com/story/001055170
!--.ftchinese.com/story/001055202
!--.ftchinese.com/story/001055242
!--.ftchinese.com/story/001055263
!--.ftchinese.com/story/001055274
!--.ftchinese.com/story/001055299
!--.ftchinese.com/story/001055480
!--.ftchinese.com/story/001055551
!--.ftchinese.com/story/001055559
!--.ftchinese.com/story/001055566
!--.ftchinese.com/story/001055840
!--.ftchinese.com/story/001056099
!--.ftchinese.com/story/001056108
!--.ftchinese.com/story/001056131
!--.ftchinese.com/story/001056375
!--.ftchinese.com/story/001056491
!--.ftchinese.com/story/001056529
!--.ftchinese.com/story/001056534
!--.ftchinese.com/story/001056538
!--.ftchinese.com/story/001056541
!--.ftchinese.com/story/001056554
!--.ftchinese.com/story/001056557
!--.ftchinese.com/story/001056560
!--.ftchinese.com/story/001056567
!--.ftchinese.com/story/001056574
!--.ftchinese.com/story/001056588
!--.ftchinese.com/story/001056594
!--.ftchinese.com/story/001056596
!--.ftchinese.com/story/001056684
!--.ftchinese.com/story/001056832
!--.ftchinese.com/story/001056833
!--.ftchinese.com/story/001056851
!--.ftchinese.com/story/001056874
!--.ftchinese.com/story/001056896
!--.ftchinese.com/story/001056927
!--.ftchinese.com/story/001057011
!--.ftchinese.com/story/001057018
!--.ftchinese.com/story/001057044
!--.ftchinese.com/story/001057162
!--.ftchinese.com/story/001057500
!--.ftchinese.com/story/001057504
!--.ftchinese.com/story/001057509
!--.ftchinese.com/story/001057518
!--.ftchinese.com/story/001057532
!--.ftchinese.com/story/001057533
!--.ftchinese.com/story/001057556
!--.ftchinese.com/story/001057580
!--.ftchinese.com/story/001057638
!--.ftchinese.com/story/001057644
!--.ftchinese.com/story/001057817
!--.ftchinese.com/story/001057875
!--.ftchinese.com/story/001058009
!--.ftchinese.com/story/001058056
!--.ftchinese.com/story/001058224
!--.ftchinese.com/story/001058257
!--.ftchinese.com/story/001058295
!--.ftchinese.com/story/001058328
!--.ftchinese.com/story/001058339
!--.ftchinese.com/story/001058344
!--.ftchinese.com/story/001058352
!--.ftchinese.com/story/001058413
!--.ftchinese.com/story/001058421
!--.ftchinese.com/story/001058440
!--.ftchinese.com/story/001058458
!--.ftchinese.com/story/001058468
!--.ftchinese.com/story/001058561
!--.ftchinese.com/story/001058566
!--.ftchinese.com/story/001058567
!--.ftchinese.com/story/001058585
!--.ftchinese.com/story/001058628
!--.ftchinese.com/story/001058656
!--.ftchinese.com/story/001058665
!--.ftchinese.com/story/001058678
!--.ftchinese.com/story/001058691
!--.ftchinese.com/story/001058721
!--.ftchinese.com/story/001058728
!--.ftchinese.com/story/001059464
!--.ftchinese.com/story/001059484
!--.ftchinese.com/story/001059537
!--.ftchinese.com/story/001059538
!--.ftchinese.com/story/001059551
!--.ftchinese.com/story/001059818
!--.ftchinese.com/story/001059914
!--.ftchinese.com/story/001059920
!--.ftchinese.com/story/001059957
!--.ftchinese.com/story/001060088
!--.ftchinese.com/story/001060156
!--.ftchinese.com/story/001060157
!--.ftchinese.com/story/001060160
!--.ftchinese.com/story/001060181
!--.ftchinese.com/story/001060185
!--.ftchinese.com/story/001060493
!--.ftchinese.com/story/001060495
!--.ftchinese.com/story/001060590
!--.ftchinese.com/story/001060846
!--.ftchinese.com/story/001060847
!--.ftchinese.com/story/001060875
!--.ftchinese.com/story/001060921
!--.ftchinese.com/story/001060946
!--.ftchinese.com/story/001061120
!--.ftchinese.com/story/001061474
!--.ftchinese.com/story/001061524
!--.ftchinese.com/story/001061642
!--.ftchinese.com/story/001062017
!--.ftchinese.com/story/001062020
!--.ftchinese.com/story/001062028
!--.ftchinese.com/story/001062092
!--.ftchinese.com/story/001062096
!--.ftchinese.com/story/001062147
!--.ftchinese.com/story/001062176
!--.ftchinese.com/story/001062188
!--.ftchinese.com/story/001062254
!--.ftchinese.com/story/001062374
!--.ftchinese.com/story/001062482
!--.ftchinese.com/story/001062496
!--.ftchinese.com/story/001062501
!--.ftchinese.com/story/001062508
!--.ftchinese.com/story/001062519
!--.ftchinese.com/story/001062554
!--.ftchinese.com/story/001062741
!--.ftchinese.com/story/001062794
!--.ftchinese.com/story/001063160
!--.ftchinese.com/story/001063359
!--.ftchinese.com/story/001063512
!--.ftchinese.com/story/001063668
!--.ftchinese.com/story/001063692
!--.ftchinese.com/story/001063763
!--.ftchinese.com/story/001063764
!--.ftchinese.com/story/001063826
!--.ftchinese.com/story/001064127
!--.ftchinese.com/story/001064312
!--.ftchinese.com/story/001064705
!--.ftchinese.com/story/001064807
!--.ftchinese.com/story/001065120
!--.ftchinese.com/story/001065168
!--.ftchinese.com/story/001065249
!--.ftchinese.com/story/001065287
!--.ftchinese.com/story/001065335
!--.ftchinese.com/story/001065337
!--.ftchinese.com/story/001065541
!--.ftchinese.com/story/001065715
!--.ftchinese.com/story/001065735
!--.ftchinese.com/story/001065756
!--.ftchinese.com/story/001065802
!--.ftchinese.com/story/001066112
!--.ftchinese.com/story/001066136
!--.ftchinese.com/story/001066140
!--.ftchinese.com/story/001066465
!--.ftchinese.com/story/001066881
!--.ftchinese.com/story/001066950
!--.ftchinese.com/story/001066959
!--.ftchinese.com/story/001067435
!--www.ftchinese.com/story/001067479
!--.ftchinese.com/story/001067528
!--.ftchinese.com/story/001067545
!--.ftchinese.com/story/001067572
!--.ftchinese.com/story/001067648
!--.ftchinese.com/story/001067650
!--.ftchinese.com/story/001067680
!--.ftchinese.com/story/001067692
!--.ftchinese.com/story/001067871
!--.ftchinese.com/story/001067923
!--.ftchinese.com/story/001068062
!--.ftchinese.com/story/001068248
!--.ftchinese.com/story/001068278
!--.ftchinese.com/story/001068379
!--.ftchinese.com/story/001068483
!--.ftchinese.com/story/001068506
!--.ftchinese.com/story/001068547
!--.ftchinese.com/story/001068616
!--.ftchinese.com/story/001068622
!--.ftchinese.com/story/001068707
!--.ftchinese.com/story/001069146
!--.ftchinese.com/story/001069373
!--.ftchinese.com/story/001069516
!--.ftchinese.com/story/001069517
!--.ftchinese.com/story/001069687
!--.ftchinese.com/story/001069741
!--.ftchinese.com/story/001069861
!--.ftchinese.com/story/001069952
!--.ftchinese.com/story/001070053
!--.ftchinese.com/story/001070177
!--.ftchinese.com/story/001070307
!--.ftchinese.com/story/001070809
!--.ftchinese.com/story/001070990
!--.ftchinese.com/story/001071042
!--.ftchinese.com/story/001071044
!--.ftchinese.com/story/001071106
!--.ftchinese.com/story/001071166
!--.ftchinese.com/story/001071181
!--ftchinese.com/story/001071200
!--.ftchinese.com/story/001071208
!--.ftchinese.com/story/001071238
!--.ftchinese.com/story/001071683
!--.ftchinese.com/story/001072271
!--.ftchinese.com/story/001072348
!--.ftchinese.com/story/001072677
!--.ftchinese.com/story/001072726
!--.ftchinese.com/story/001072794
!--.ftchinese.com/story/001072853
!--.ftchinese.com/story/001072895
!--.ftchinese.com/story/001072993
!--.ftchinese.com/story/001073043
!--.ftchinese.com/story/001073103
!--.ftchinese.com/story/001073157
!--.ftchinese.com/story/001073216
!--.ftchinese.com/story/001073246
!--.ftchinese.com/story/001073305
!--.ftchinese.com/story/001073307
!--.ftchinese.com/story/001073408
!--.ftchinese.com/story/001073537
!--.ftchinese.com/story/001073672
!--.ftchinese.com/story/001073849
!--.ftchinese.com/story/001073906
!--.ftchinese.com/story/001074089
!--.ftchinese.com/story/001074110
!--.ftchinese.com/story/001074128
!--.ftchinese.com/story/001074157
!--.ftchinese.com/story/001074246
!--.ftchinese.com/story/001074307
!--.ftchinese.com/story/001074347
!--.ftchinese.com/story/001074423
!--.ftchinese.com/story/001074454
!--.ftchinese.com/story/001074467
!--.ftchinese.com/story/001074493
!--.ftchinese.com/story/001074550
!--.ftchinese.com/story/001074562
!--.ftchinese.com/story/001074653
!--.ftchinese.com/story/001074693
!--.ftchinese.com/story/001074699
!--.ftchinese.com/story/001074712
!--.ftchinese.com/story/001074713
!--.ftchinese.com/story/001074768
!--.ftchinese.com/story/001074782
!--.ftchinese.com/story/001074794
!--.ftchinese.com/story/001074822
!--.ftchinese.com/story/001074874
!--.ftchinese.com/story/001074891
!--.ftchinese.com/story/001074918
!--.ftchinese.com/story/001075081
!--.ftchinese.com/story/001075134
!--.ftchinese.com/story/001075142
!--.ftchinese.com/story/001075216
!--.ftchinese.com/story/001075230
!--.ftchinese.com/story/001075238
!--.ftchinese.com/story/001075262
!--.ftchinese.com/story/001075269
!--.ftchinese.com/story/001075491
!--.ftchinese.com/story/001075500
!--.ftchinese.com/story/001075650
!--.ftchinese.com/story/001075678
!--.ftchinese.com/story/001075703
!--.ftchinese.com/story/001075739
!--.ftchinese.com/story/001076066
!--.ftchinese.com/story/001076142
!--.ftchinese.com/story/001076459
!--.ftchinese.com/story/001076470
!--.ftchinese.com/story/001076538
!--.ftchinese.com/story/001076573
!--.ftchinese.com/story/001076901
!--.ftchinese.com/story/001077067
!--.ftchinese.com/story/001077084
!--.ftchinese.com/story/001077235
!--.ftchinese.com/story/001077344
!--.ftchinese.com/story/001077390
!--.ftchinese.com/story/001077392
!--.ftchinese.com/story/001077465
!--.ftchinese.com/story/001077468
!--.ftchinese.com/story/001077492
!--.ftchinese.com/story/001077745
!--.ftchinese.com/story/001077768
!--.ftchinese.com/story/001077804
!--.ftchinese.com/story/001077852
!--.ftchinese.com/story/001078646
!--.ftchinese.com/story/001078928
!--.ftchinese.com/story/001078967
!--.ftchinese.com/story/001079559
!--.ftchinese.com/story/001079641
!--.ftchinese.com/story/001079909
!--.ftchinese.com/story/001079934
!--.ftchinese.com/story/001079992
!--.ftchinese.com/story/001080054
!--.ftchinese.com/story/001080109
!--.ftchinese.com/story/001080169
!--.ftchinese.com/story/001080226
!--.ftchinese.com/story/001080429
!--.ftchinese.com/story/001080471
!--.ftchinese.com/story/001080550
!--.ftchinese.com/story/001080581
!--.ftchinese.com/story/001080647
!--.ftchinese.com/story/001080778
!--.ftchinese.com/story/001080892
!--.ftchinese.com/story/001080915
!--.ftchinese.com/story/001080935
!--.ftchinese.com/story/001081059
!--.ftchinese.com/story/001081127
!--.ftchinese.com/tag/%E5%8D%81%E5%85%AB%E5%B1%8A%E4%B8%89%E4%B8%AD%E5%85%A8%E4%BC%9A
!--.ftchinese.com/tag/%E6%B8%A9%E5%AE%B6%E5%AE%9D
!--.ftchinese.com/tag/%E8%96%84%E7%86%99%E6%9D%A5
!--.ftchinese.com/video/1437
!--.ftchinese.com/video/1882
!--.ftchinese.com/video/2446
!--.ftchinese.com/video/2601
!--.ftchinese.com/comments
!!---Google---
!###https://www.google.com/supported_domains###
!...GFWList doesn't intend to support typosquatting...
||1e100.net
||466453.com
||abc.xyz
||about.google
||admob.com
||adsense.com
||advertisercommunity.com
||agoogleaday.com
||ai.google
||ampproject.org
@@|https://www.ampproject.org
@@|https://cdn.ampproject.org
||android.com
||androidify.com
||androidtv.com
||api.ai
.appspot.com
||appspot.com
||autodraw.com
||blog.google
||blogblog.com
blogspot.com
/^https?:\/\/[^\/]+blogspot\.(.*)/
.blogspot.hk
.blogspot.jp
.blogspot.tw
||business.page
!--||capitalg.com
||certificate-transparency.org
||chrome.com
||chromecast.com
||chromeenterprise.google
||chromeexperiments.com
||chromercise.com
||chromestatus.com
||chromium.org
||cloudfunctions.net
||com.google
||crbug.com
||creativelab5.com
||crisisresponse.google
||crrev.com
||data-vocabulary.org
||debug.com
||deepmind.com
||deja.com
||design.google
||digisfera.com
||dns.google
||hub.docker.com
||docs.new
||domains.google
||duck.com
||environment.google
||feedburner.com
||firebaseio.com
||g.co
||gcr.io
||get.app
||get.dev
||get.how
||get.page
||getmdl.io
||getoutline.org
||ggpht.com
||gmail.com
||gmodules.com
||godoc.org
||golang.org
||goo.gl
||goo.gle
.google.ae
.google.as
.google.am
.google.at
.google.az
.google.ba
.google.be
.google.bg
.google.ca
.google.cd
.google.ci
.google.co.id
.google.co.jp
.google.co.kr
.google.co.ma
.google.co.uk
.google.com
.google.de
||google.dev
.google.dj
.google.dk
.google.es
.google.fi
.google.fm
.google.fr
.google.gg
.google.gl
.google.gr
.google.ie
.google.is
.google.it
.google.jo
.google.kz
.google.lv
.google.mn
.google.ms
.google.nl
.google.nu
.google.no
.google.ro
.google.ru
.google.rw
.google.sc
.google.sh
.google.sk
.google.sm
.google.sn
.google.tk
.google.tm
.google.to
.google.tt
.google.vu
.google.ws
/^https?:\/\/([^\/]+\.)*google\.(ac|ad|ae|af|ai|al|am|as|at|az|ba|be|bf|bg|bi|bj|bs|bt|by|ca|cat|cd|cf|cg|ch|ci|cl|cm|co.ao|co.bw|co.ck|co.cr|co.id|co.il|co.in|co.jp|co.ke|co.kr|co.ls|co.ma|com|com.af|com.ag|com.ai|com.ar|com.au|com.bd|com.bh|com.bn|com.bo|com.br|com.bz|com.co|com.cu|com.cy|com.do|com.ec|com.eg|com.et|com.fj|com.gh|com.gi|com.gt|com.hk|com.jm|com.kh|com.kw|com.lb|com.ly|com.mm|com.mt|com.mx|com.my|com.na|com.nf|com.ng|com.ni|com.np|com.om|com.pa|com.pe|com.pg|com.ph|com.pk|com.pr|com.py|com.qa|com.sa|com.sb|com.sg|com.sl|com.sv|com.tj|com.tr|com.tw|com.ua|com.uy|com.vc|com.vn|co.mz|co.nz|co.th|co.tz|co.ug|co.uk|co.uz|co.ve|co.vi|co.za|co.zm|co.zw|cv|cz|de|dj|dk|dm|dz|ee|es|eu|fi|fm|fr|ga|ge|gg|gl|gm|gp|gr|gy|hk|hn|hr|ht|hu|ie|im|iq|is|it|it.ao|je|jo|kg|ki|kz|la|li|lk|lt|lu|lv|md|me|mg|mk|ml|mn|ms|mu|mv|mw|mx|ne|nl|no|nr|nu|org|pl|pn|ps|pt|ro|rs|ru|rw|sc|se|sh|si|sk|sm|sn|so|sr|st|td|tg|tk|tl|tm|tn|to|tt|us|vg|vn|vu|ws)\/.*/
!--||google-analytics.com
!--||googleadservices.com
||googleapis.cn
||googleapis.com
||googleapps.com
||googleartproject.com
||googleblog.com
||googlebot.com
!--||googlecapital.com
||googlechinawebmaster.com
||googlecode.com
||googlecommerce.com
||googledomains.com
||googlearth.com
||googleearth.com
||googledrive.com
||googlefiber.net
||googlegroups.com
||googlehosted.com
||googleideas.com
||googleinsidesearch.com
||googlelabs.com
||googlemail.com
||googlemashups.com
||googlepagecreator.com
||googleplay.com
||googleplus.com
||googlescholar.comUSA
||googlesource.com
!--||googlesyndication.com
!--||googletagmanager.com
!--||googletagservices.com
||googleusercontent.com
.googlevideo.com
||googlevideo.com
||googleweblight.com
||googlezip.net
||groups.google.cn
||grow.google
||gstatic.com
!--||gv.com
||gvt0.com
||gvt1.com
@@||redirector.gvt1.com
||gvt3.com
||gwtproject.org
||html5rocks.com
||iam.soy
||igoogle.com
||itasoftware.com
||lers.google
||like.com
||madewithcode.com
||material.io
||nic.google
||on2.com
||opensource.google
||panoramio.com
||passwords.google
||picasaweb.com
||pki.goog
||plus.codes
||polymer-project.org
||pride.google
||questvisual.com
||admin.recaptcha.net
||api.recaptcha.net
||api-secure.recaptcha.net
||api-verify.recaptcha.net
||redhotlabs.com
||registry.google
||research.google
||safety.google
||savethedate.foo
||schema.org
||shattered.io
|http://sipml5.org/
||sheets.new
||slides.new
||snapseed.com
||stories.google
||sustainability.google
||synergyse.com
||teachparentstech.org
||tensorflow.org
||tfhub.dev
||thinkwithgoogle.com
||tiltbrush.com
||translate.goog
||tv.google
||urchin.com
!--||www.google
||waveprotocol.org
||waymo.com
||web.dev
||webmproject.org
||webpkgcache.com
||webrtc.org
||whatbrowser.org
||whats.new
||widevine.com
||withgoogle.com
||withyoutube.com
||x.company
||xn--ngstr-lra8j.com
||youtu.be
.youtube.com
||youtube.com
||youtube-nocookie.com
||youtubeeducation.com
||youtubegaming.com
||youtubekids.com
||yt.be
||ytimg.com
||zynamics.com
!!---KickASS---
!--OFFICIAL URL list at: https://kastatus.com
!!---NaughtyAmerica---
||naughtyamerica.com
!!---NYTimes---
!--||d1f1eryiqyjs0r.cloudfront.net
!--||d3lar09xbwlsge.cloudfront.net
!--||d3q1qj9jzsu8nw.cloudfront.net
!--||dc8xl0ndzn2cb.cloudfront.net
!--||a1.nyt.com
!--||int.nyt.com
!--||s1.nyt.com
static01.nyt.com
!--||static01.nyt.com
!--||typeface.nyt.com
||nyt.com
nytchina.com
nytcn.me
||nytcn.me
||nytco.com
|http://nyti.ms/
.nytimes.com
||nytimes.com
||nytimg.com
userapi.nytlog.com
cn.nytstyle.com
||nytstyle.com
!!---Steam---
.steamcommunity.com
||steamcommunity.com
!--steamcommunity.com/profiles/76561198062771609
!--steamcommunity.com/groups/LibetTibet
!--steamcommunity.com/groups/zhonggong
!--steamcommunity.com/id/CJT_Jackton
||store.steampowered.com
!!---Telegram---
!!!---Domain---
||cdn-telegram.org
||comments.app
||graph.org
||quiz.directory
||t.me
||updates.tdesktop.com
||telegram.dog
||telegram.me
||telegram.org
||telegram.space
||telegram-cdn.org
||telegramdownload.com
||telegra.ph
||telesco.pe
!!!---IP---
!!---Tiktok---
||tiktok.com
||tiktokv.com
||tiktokv.us
||tiktokcdn-us.com
!!---Twitch---
||jtvnw.net
||ttvnw.net
||twitch.tv
||twitchcdn.net
!!---Twitter/X---
||periscope.tv
.pscp.tv
||pscp.tv
.t.co
||t.co
.tweetdeck.com
||tweetdeck.com
||twimg.com
.twitpic.com
||twitpic.com
.twitter.com
||twitter.com
||twitter.jp
||vine.co
||x.com
!!---Taiwan---
||gov.taipei
.gov.tw
|https://aiss.anws.gov.tw
||archives.gov.tw
||tacc.cwb.gov.tw
||data.gov.tw
||epa.gov.tw
||fa.gov.tw
||fda.gov.tw
||hpa.gov.tw
||immigration.gov.tw
||itaiwan.gov.tw
||li.taipei
||mjib.gov.tw
||moeaic.gov.tw
||mofa.gov.tw
||mol.gov.tw
||mvdis.gov.tw
||nat.gov.tw
||nhi.gov.tw
||npa.gov.tw
||nsc.gov.tw
||ntbk.gov.tw
||ntbna.gov.tw
||ntbt.gov.tw
||ntsna.gov.tw
||pcc.gov.tw
||stat.gov.tw
||taipei.gov.tw
||taiwanjobs.gov.tw
||thb.gov.tw
||tipo.gov.tw
||wda.gov.tw
||teco-hk.org
||teco-mo.org
@@||aftygh.gov.tw
@@||aide.gov.tw
@@||tpde.aide.gov.tw
@@||arte.gov.tw
@@||chukuang.gov.tw
@@||cwb.gov.tw
@@||cycab.gov.tw
@@||dbnsa.gov.tw
@@||df.gov.tw
@@||eastcoast-nsa.gov.tw
@@||erv-nsa.gov.tw
@@||grb.gov.tw
@@||gysd.nyc.gov.tw
@@||hchcc.gov.tw
@@||hsinchu-cc.gov.tw
@@||iner.gov.tw
@@||klsio.gov.tw
@@||kmseh.gov.tw
@@||lungtanhr.gov.tw
@@||maolin-nsa.gov.tw
@@||matsu-news.gov.tw
@@||matsu-nsa.gov.tw
@@||matsucc.gov.tw
@@||moe.gov.tw
@@||nankan.gov.tw
@@||ncree.gov.tw
@@||necoast-nsa.gov.tw
@@||siraya-nsa.gov.tw
@@||cromotc.nat.gov.tw
@@||tax.nat.gov.tw
@@||necoast-nsa.gov.tw
@@||ner.gov.tw
@@||nmmba.gov.tw
@@||nmp.gov.tw
@@||nmvttc.gov.tw
@@||northguan-nsa.gov.tw
||npm.gov.tw
@@||nstm.gov.tw
@@||ntdmh.gov.tw
@@||ntl.gov.tw
@@||ntsec.gov.tw
@@||ntuh.gov.tw
@@||nvri.gov.tw
@@||penghu-nsa.gov.tw
@@||post.gov.tw
@@||siraya-nsa.gov.tw
@@||stdtime.gov.tw
@@||sunmoonlake.gov.tw
@@||taitung-house.gov.tw
@@||taoyuan.gov.tw
@@||tphcc.gov.tw
@@||trimt-nsa.gov.tw
@@||vghtpe.gov.tw
@@||vghks.gov.tw
@@||vghtc.gov.tw
@@||wanfang.gov.tw
@@||yatsen.gov.tw
@@||yda.gov.tw
!--@@||4pppc.gov.tw
!--@@||921.gov.tw
!--@@||dmtip.gov.tw
!--@@||etraining.gov.tw
!--@@||gsn-cert.nat.gov.tw
!--@@||nici.nat.gov.tw
!--@@||hcc.gov.tw
!--@@||hengchuen.gov.tw
!--@@||khcc.gov.tw
!--@@||khms.gov.tw
!--@@||kk.gov.tw
!--@@||klccab.gov.tw
!--@@||klra.gov.tw
!--@@||nmh.gov.tw
!--@@||nmtl.gov.tw
!--@@||pabp.gov.tw
!--@@||pet.gov.tw
!--@@||tchb.gov.tw
!--@@||tcsac.gov.tw
!--@@||tncsec.gov.tw
||kinmen.org.tw
!!---USA---
|http://www.americorps.gov
||jpl.nasa.gov
||pds.nasa.gov
||solarsystem.nasa.gov
iipdigital.usembassy.gov
||usfk.mil
||usmc.mil
|http://tarr.uspto.gov/
||tsdr.uspto.gov
!!---V2EX---
||v2ex.com
!--.v2ex.com
!--Included in above rule: dns.v2ex.com
!--@@|http://v2ex.com
!--@@|http://cdn.v2ex.com
!--@@|http://cn.v2ex.com
!--@@|http://hk.v2ex.com
!--@@|http://i.v2ex.com
!--@@|http://lax.v2ex.com
!--@@|http://neue.v2ex.com
!--@@|http://pagespeed.v2ex.com
!--@@|http://static.v2ex.com
!--@@|http://workspace.v2ex.com
!--@@|http://www.v2ex.com
!!---VOA---
cn.voa.mobi
tw.voa.mobi
||voacambodia.com
.voachineseblog.com
||voachineseblog.com
.voacantonese.com
||voacantonese.com
voachinese.com
||voachinese.com
voagd.com
||voaindonesia.com
.voanews.com
||voanews.com
voatibetan.com
||voatibetan.com
.voatibetanenglish.com
||voatibetanenglish.com
!!---Wikia---
||zh.ecdm.wikia.com
||evchk.wikia.com
fq.wikia.com
zh.pttpedia.wikia.com/wiki/%E7%BF%92%E5%8C%85%E5%AD%90%E4%B9%8B%E4%BA%82
cn.uncyclopedia.wikia.com
zh.uncyclopedia.wikia.com
!-------------Wikipedia Related-------------
!!Emergency need only(IP/Port block usage)!!
!------0------
!--||mediawiki.org
!--@@||m.mediawiki.org
!------1------
!--||wikidata.org
!--@@||m.wikidata.org
!------2------
||wikimedia.org
!--@@||lists.wikimedia.org
!--@@||m.wikimedia.org
!--@@||phabricator.wikimedia.org
!--@@||upload.wikimedia.org
!--@@||wikitech.wikimedia.org
!------3------
!--||wikibooks.org
!--@@||m.wikibooks.org
!------4------
!--||wikiversity.org
!--@@||m.wikiversity.org
!------5------
!--||wikisource.org
!--@@||m.wikisource.org
|http://zh.wikisource.org
!------6------
||zh.wikiquote.org
!--@@||m.wikiquote.org
!------7------
!--||wikinews.org
!--@@||m.wikinews.org
||zh.wikinews.org
!------8------
!--||wikivoyage.org
!--@@||m.wikivoyage.org
!--|http://zh.wikivoyage.org
!------9------
!--||wiktionary.org
!--@@||m.wiktionary.org
!--|http://zh.wiktionary.org
!-----10------
!--||wikimediafoundation.org
!--@@||m.wikimediafoundation.org
!----Main-----
!!--||en.wikipedia.org
!--||wikipedia.org
||ja.wikipedia.org
!!--zh.wikipedia.org
!--||zh.wikipedia.org
!!--||ug.m.wikipedia.org
!!--zh.m.wikipedia.org
!!--|https://zh.m.wikipedia.org
!--@@||m.wikipedia.org
!!--|https://zh.wikipedia.org
!--Other Languages of Wikipedia
!!--wuu.wikipedia.org
!!--|https://wuu.wikipedia.org
!!--zh-yue.wikipedia.org
!!--|https://zh-yue.wikipedia.org
!!! Starting with !! are previous rules replaced by:
||wikipedia.org
!!---Yahoo---
||data.flurry.com
||page.bid.yahoo.com
||tw.bid.yahoo.com
||auctions.yahoo.co.jp
||blogs.yahoo.co.jp
||search.yahoo.co.jp
||buy.yahoo.com.tw
||hk.yahoo.com
||hk.knowledge.yahoo.com
||tw.money.yahoo.com
||hk.myblog.yahoo.com
news.yahoo.com/china-blocks-bbc
||hk.news.yahoo.com
hk.rd.yahoo.com
hk.search.yahoo.com/search
hk.video.news.yahoo.com/video
meme.yahoo.com
!--tw.yahoo.com
tw.answers.yahoo.com
|https://tw.answers.yahoo.com
||tw.knowledge.yahoo.com
||tw.mall.yahoo.com
tw.yahoo.com
||tw.mobi.yahoo.com
tw.myblog.yahoo.com
||tw.news.yahoo.com
pulse.yahoo.com
||search.yahoo.com
upcoming.yahoo.com
video.yahoo.com
||yahoo.com.hk
||duckduckgo-owned-server.yahoo.net
!------------------Numerics---------------------
||000webhost.com
.030buy.com
.0rz.tw
|http://0rz.tw
1-apple.com.tw
||1-apple.com.tw
.10.tt
.100ke.org
.1000giri.net
||1000giri.net
||10beasts.net
.10conditionsoflove.com
||10musume.com
123rf.com
.12bet.com
||12bet.com
.12vpn.com
.12vpn.net
||12vpn.com
||12vpn.net
||1337x.to
.138.com
141hongkong.com/forum
||141jj.com
.141tube.com
||1688.com.au
.173ng.com
||173ng.com
.177pic.info
.17t17p.com
||18board.com
||18board.info
18onlygirls.com
.18p2p.com
.18virginsex.com
.1949er.org
zhao.1984.city
||zhao.1984.city
1984bbs.com
||1984bbs.com
!--||1984blog.com
.1984bbs.org
||1984bbs.org
.1991way.com
||1991way.com
.1998cdp.org
.1bao.org
|http://1bao.org
.1eew.com
.1mobile.com
|http://*.1mobile.tw
||1point3acres.com
||1pondo.tv
.2-hand.info
.2000fun.com/bbs
||2008xianzhang.info
||2017.hk
||2021hkcharter.com
||2047.name
21andy.com/blog
21sextury.com
.228.net.tw
||233abc.com
||24hrs.ca
24smile.org
2lipstube.com
.2shared.com
30boxes.com
.315lz.com
||32red.com
||36rain.com
.3a5a.com
3arabtv.com
.3boys2girls.com
.3proxy.ru
.3ren.ca
.3tui.net
||404museum.com
||4bluestones.biz
.4chan.com
!--||4chan.org
.4everproxy.com
||4everproxy.com
||4rbtv.com
||4shared.com
taiwannation.50webs.com
||51.ca
||51jav.org
.51luoben.com
||51luoben.com
||5278.cc
.5299.tv
5aimiku.com
5i01.com
.5isotoi5.org
.5maodang.com
||63i.com
.64museum.org
64tianwang.com
64wiki.com
.66.ca
666kb.com
||6do.news
.6park.com
||6park.com
||6parkbbs.com
||6parker.com
||6parknews.com
||7capture.com
.7cow.com
!--||7-zip.org
.8-d.com
|http://8-d.com
85cc.net
.85cc.us
|http://85cc.us
|http://85st.com
.881903.com/page/zh-tw/
||881903.com
.888.com
.888poker.com
89.64.charter.constitutionalism.solutions
89-64.org
||89-64.org
||8964museum.com
.8news.com.tw
.8z1.net
||8z1.net
.9001700.com
|http://908taiwan.org/
||91porn.com
||91porny.com
||91vps.club
.92ccav.com
.991.com
|http://991.com
.99btgc01.com
||99btgc01.com
.99cn.info
|http://99cn.info
||9bis.com
||9bis.net
||9news.com.au
!--------------------AA-------------------------
.tibet.a.se
|http://tibet.a.se
||a-normal-day.com
a5.com.ru
|http://aamacau.com
!--|http://cdn*.abc.com/
.abc.com
.abc.net.au
||abc.net.au
.abchinese.com
abclite.net
|https://www.abclite.net
.ablwang.com
.aboluowang.com
||aboluowang.com
||about.me
.aboutgfw.com
.abs.edu
||acast.com
.accim.org
.aceros-de-hispania.com
.acevpn.com
||acevpn.com
.acg18.me
|http://acg18.me
||acgbox.org
||acgkj.com
||acgnx.se
.acmedia365.com
.acnw.com.au
actfortibet.org
actimes.com.au
activpn.com
||activpn.com
||aculo.us
||addictedtocoffee.de
||addyoutube.com
.adelaidebbs.com/bbs
.adpl.org.hk
|http://adpl.org.hk
.adult-sex-games.com
||adult-sex-games.com
adultfriendfinder.com
adultkeep.net/peepshow/members/main.htm
||advanscene.com
||advertfan.com
.ae.org
||aei.org
||aenhancers.com
||af.mil
.afantibbs.com
|http://afantibbs.com
||afr.com
.ai-kan.net
||ai-kan.net
ai-wen.net
.aiph.net
||aiph.net
.airasia.com
||airconsole.com
|http://download.aircrack-ng.org
.airvpn.org
||airvpn.org
.aisex.com
||ait.org.tw
aiweiwei.com
.aiweiweiblog.com
||aiweiweiblog.com
||www.ajsands.com
!!---Akamai---
a248.e.akamai.net
||a248.e.akamai.net
rfalive1.akacast.akamaistream.net
voa-11.akacast.akamaistream.net
!!--403
||abematv.akamaized.net
||linear-abematv.akamaized.net
||vod-abematv.akamaized.net
|https://fbcdn*.akamaihd.net/
!--||fbexternal-a.akamaihd.net
!--||fbstatic-a.akamaihd.net
!--|https://igcdn*.akamaihd.net
rthklive2-lh.akamaihd.net
.akademiye.org/ug
|http://akademiye.org/ug
||akiba-online.com
||akow.org
.al-islam.com
||al-qimmah.net
||alabout.com
.alanhou.com
|http://alanhou.com
.alarab.qa
||alasbarricadas.org
alexlur.org
||alforattv.net
.alhayat.com
.alicejapan.co.jp
aliengu.com
||alive.bar
||alkasir.com
||all4mom.org
||allconnected.co
.alldrawnsex.com
||alldrawnsex.com
.allervpn.com
||allfinegirls.com
.allgirlmassage.com
allgirlsallowed.org
.allgravure.com
alliance.org.hk
.allinfa.com
||allinfa.com
.alljackpotscasino.com
||allmovie.com
||almasdarnews.com
.alphaporno.com
||alternate-tools.com
alternativeto.net/software
alvinalexander.com
alwaysdata.com
||alwaysdata.com
||alwaysdata.net
.alwaysvpn.com
||alwaysvpn.com
||am730.com.hk
ameblo.jp
||ameblo.jp
www1.american.edu/ted/ice/tibet
||americangreencard.com
||amiblockedornot.com
.amigobbs.net
.amitabhafoundation.us
|http://amitabhafoundation.us
.amnesty.org
||amnesty.org
||amnesty.org.hk
.amnesty.tw
.amnestyusa.org
||amnestyusa.org
.amnyemachen.org
.amoiist.com
.amtb-taipei.org
androidplus.co/apk
.andygod.com
|http://andygod.com
annatam.com/chinese
||anchor.fm
||anchorfree.com
!--GHS
||ancsconf.org
||andfaraway.net
||android-x86.org
angelfire.com/hi/hayashi
||angularjs.org
animecrazy.net
aniscartujo.com
||aniscartujo.com
||anobii.com
||anonfiles.com
.anonymitynetwork.com
.anonymizer.com
.anonymouse.org
||anonymouse.org
anontext.com
.anpopo.com
.answering-islam.org
|http://www.antd.org
||anthonycalzadilla.com
.anti1984.com
antichristendom.com
.antiwave.net
|http://antiwave.net
.anyporn.com
.anysex.com
|http://anysex.com
.ao3.org
||ao3.org
||aobo.com.au
.aofriend.com
|http://aofriend.com
.aofriend.com.au
.aojiao.org
||aomiwang.com
video.ap.org
||apat1989.org
.apetube.com
||apiary.io
.apigee.com
||apigee.com
||apk.support
||apk-dl.com
||apkcombo.com
.apkmonk.com/app
||apkmonk.com
||apkplz.com
||apkpure.com
||apkpure.net
.aplusvpn.com
!--||appannie.com
||appbrain.com
.appdownloader.net/Android
.appledaily.com
||appledaily.com
appledaily.com.hk
||appledaily.com.hk
appledaily.com.tw
||appledaily.com.tw
.appshopper.com
|http://appshopper.com
||appsocks.net
||appsto.re
.aptoide.com
||aptoide.com
||archives.gov
.archive.fo
||archive.fo
.archive.is
||archive.is
.archive.li
||archive.li
||archive.md
||archive.org
||archive.ph
archive.today
|https://archive.today
||archiveofourown.com
||archiveofourown.org
.arctosia.com
|http://arctosia.com
||areca-backup.org
.arethusa.su
||arethusa.su
||arlingtoncemetery.mil
||army.mil
.art4tibet1998.org
artofpeacefoundation.org
artsy.net
||asacp.org
asdfg.jp/dabr
asg.to
.asia-gaming.com
.asiaharvest.org
||asiaharvest.org
||asianage.com
||asianews.it
|http://japanfirst.asianfreeforum.com/
||asiansexdiary.com
||asianwomensfilm.de
||asiaone.com
.asiatgp.com
.asiatoday.us
||askstudent.com
.askynz.net
||askynz.net
||aspi.org.au
||aspistrategist.org.au
||assembla.com
||astrill.com
||atc.org.au
.atchinese.com
|http://atchinese.com
atgfw.org
.atlaspost.com
||atlaspost.com
||atdmt.com
.atlanta168.com
||atlanta168.com
.atnext.com
||atnext.com
||audacy.com
ice.audionow.com
.av.com
||av.movie
.av-e-body.com
avaaz.org
||avaaz.org
!--||avast.com
.avbody.tv
.avcity.tv
.avcool.com
.avdb.in
||avdb.in
.avdb.tv
||avdb.tv
.avfantasy.com
||avg.com
.avgle.com
||avgle.com
||avidemux.org
||avoision.com
.avyahoo.com
||axios.com
||axureformac.com
.azerbaycan.tv
azerimix.com
||azirevpn.com
!--boxun.azurewebsites.net doesn't exist.
boxun*.azurewebsites.net
||boxun*.azurewebsites.net
!--------------------BB-------------------------
||b-ok.cc
forum.baby-kingdom.com
||babylonbee.com
babynet.com.hk
backchina.com
||backchina.com
.backpackers.com.tw/forum
backtotiananmen.com
||bad.news
.badiucao.com
||badiucao.com
.badjojo.com
badoo.com
|http://*2.bahamut.com.tw
||baidu.jp
.baijie.org
||baijie.org
||bailandaily.com
||baixing.me
||baizhi.org
||bakgeekhome.tk
.banana-vpn.com
||banana-vpn.com
||band.us
||bandcamp.com
.bandwagonhost.com
||bandwagonhost.com
.bangbrosnetwork.com
.bangchen.net
|http://bangchen.net
||bangkokpost.com
||bangyoulater.com
bannedbook.org
||bannedbook.org
.bannednews.org
.baramangaonline.com
|http://baramangaonline.com
.barenakedislam.com
||barnabu.co.uk
||barton.de
.bastillepost.com
||bastillepost.com
bayvoice.net
||bayvoice.net
dajusha.baywords.com
||bbchat.tv
||bb-chat.tv
.bbg.gov
.bbkz.com/forum
.bbnradio.org
bbs-tw.com
.bbsdigest.com/thread
||bbsfeed.com
bbsland.com
.bbsmo.com
.bbsone.com
bbtoystore.com
.bcast.co.nz
.bcc.com.tw/board
.bcchinese.net
.bcmorning.com
bdsmvideos.net
.beaconevents.com
.bebo.com
||bebo.com
.beevpn.com
||beevpn.com
.behindkink.com
||beijing1989.com
||beijing2022.art
beijingspring.com
||beijingspring.com
.beijingzx.org
|http://beijingzx.org
.belamionline.com
.bell.wiki
|http://bell.wiki
bemywife.cc
beric.me
||berlinerbericht.de
.berlintwitterwall.com
||berlintwitterwall.com
.berm.co.nz
.bestforchina.org
||bestforchina.org
.bestgore.com
.bestpornstardb.com
||bestvpn.com
.bestvpnanalysis.com
.bestvpnserver.com
.bestvpnservice.com
.bestvpnusa.com
||bet365.com
.betfair.com
||betternet.co
.bettervpn.com
||bettervpn.com
.bettween.com
||bettween.com
||betvictor.com
.bewww.net
.beyondfirewall.com
||bfnn.org
||bfsh.hk
.bgvpn.com
||bgvpn.com
.bianlei.com
@@||bianlei.com
biantailajiao.com
biantailajiao.in
.biblesforamerica.org
|http://biblesforamerica.org
.bic2011.org
||biedian.me
bigfools.com
||bigjapanesesex.com
.bignews.org
||bignews.org
.bigsound.org
||bild.de
.biliworld.com
|http://biliworld.com
|http://billypan.com/wiki
.binux.me
ai.binwang.me/couplet
.bit.do
|http://bit.do
.bit.ly
|http://bit.ly
!--||bitbucket.org
||bitchute.com
||bitcointalk.org
.bitshare.com
||bitshare.com
bitsnoop.com
.bitvise.com
||bitvise.com
bizhat.com
||bl-doujinsouko.com
.bjnewlife.org
.bjs.org
bjzc.org
||bjzc.org
.blacklogic.com
.blackvpn.com
||blackvpn.com
blewpass.com
tor.blingblingsquad.net
.blinkx.com
||blinkx.com
blinw.com
.blip.tv
||blip.tv/
||blockcast.it
.blockcn.com
||blockcn.com
||blockedbyhk.com
||blockless.com
||blog.de
.blog.jp
|http://blog.jp
@@||jpush.cn
.blogcatalog.com
||blogcatalog.com
||blogcity.me
.blogger.com
||blogger.com
blogimg.jp
||blog.kangye.org
.bloglines.com
||bloglines.com
||bloglovin.com
rconversation.blogs.com
blogtd.net
.blogtd.org
|http://blogtd.org
||bloodshed.net
!--403
||assets.bwbx.io
||bloomfortune.com
blueangellive.com
||blubrry.com
.bmfinn.com
.bnews.co
||bnews.co
||bnext.com.tw
||bnrmetal.com
boardreader.com/thread
||boardreader.com
.bod.asia
||bod.asia
.bodog88.com
.bolehvpn.net
||bolehvpn.net
bonbonme.com
.bonbonsex.com
.bonfoundation.org
.bongacams.com
||boobstagram.com
||book.com.tw
||bookdepository.com
bookepub.com
||books.com.tw
||borgenmagazine.com
||botanwang.com
.bot.nu
.bowenpress.com
||bowenpress.com
||app.box.com
dl.box.net
||dl.box.net
.boxpn.com
||boxpn.com
boxun.com
||boxun.com
.boxun.tv
||boxun.tv
boxunblog.com
||boxunblog.com
.boxunclub.com
boyangu.com
.boyfriendtv.com
.boysfood.com
||br.st
.brainyquote.com/quotes/authors/d/dalai_lama
||brandonhutchinson.com
||braumeister.org
||brave.com
.bravotube.net
||bravotube.net
.brazzers.com
||brazzers.com
||breached.to
.break.com
||break.com
breakgfw.com
||breakgfw.com
breaking911.com
.breakingtweets.com
||breakingtweets.com
||breakwall.net
briian.com/6511/freegate
.briefdream.com/%E7%B4%A0%E6%A3%BA
||brill.com
brizzly.com
||brizzly.com
||brkmd.com
broadbook.com
.broadpressinc.com
||broadpressinc.com
bbs.brockbbs.com
||brookings.edu
brucewang.net
.brutaltgp.com
||brutaltgp.com
||bsky.app
||bsky.social
||bt95.com
.btaia.com
.btbtav.com
||btdig.com
||btdigg.org
.btku.me
||btku.me
||btku.org
.btspread.com
.btsynckeys.com
.budaedu.org
||budaedu.org
.buddhanet.com.tw/zfrop/tibet
||buffered.com
||bullguard.com
.bullog.org
||bullog.org
.bullogger.com
||bullogger.com
||bumingbai.net
||bunbunhk.com
.busayari.com
|http://busayari.com
||business-humanrights.org
.businessinsider.com/bing-could-be-censoring-search-results-2014
.businessinsider.com/china-banks-preparing-for-debt-implosion-2014
.businessinsider.com/hong-kong-activists-defy-police-tear-gas-as-protests-continue-overnight-2014
.businessinsider.com/internet-outages-reported-in-north-korea-2014
.businessinsider.com/iphone-6-is-approved-for-sale-in-china-2014
.businessinsider.com/nfl-announcers-surface-tablets-2014
.businessinsider.com/panama-papers
.businessinsider.com/umbrella-man-hong-kong-2014
|http://www.businessinsider.com.au/*
.businesstoday.com.tw
||businesstoday.com.tw
.busu.org/news
|http://busu.org/news
busytrade.com
.buugaa.com
.buzzhand.com
.buzzhand.net
.buzzorange.com
||buzzorange.com
||buzzsprout.com
||bvpn.com
||bwh1.net
bwsj.hk
||bx.tl
||bypasscensorship.org
!--------------------CC-------------------------
||c-span.org
.c-spanvideo.org
||c-spanvideo.org
||c-est-simple.com
.c100tibet.org
||cableav.tv
||cablegatesearch.net
.cachinese.com
.cacnw.com
|http://cacnw.com
.cactusvpn.com
||cactusvpn.com
.cafepress.com
.cahr.org.tw
.caijinglengyan.com
||caijinglengyan.com
.calameo.com/books
||calendarz.com
.calgarychinese.ca
.calgarychinese.com
.calgarychinese.net
|http://blog.calibre-ebook.com
falun.caltech.edu
.its.caltech.edu/~falun/
.cam4.com
.cam4.jp
.cam4.sg
.camfrog.com
||camfrog.com
||campaignforuyghurs.org
||cams.com
.cams.org.sg
canadameet.com
.canalporno.com
|http://bbs.cantonese.asia/
!--http://www.cantonese.asia/action-bbs.html
.canyu.org
||canyu.org
.cao.im
.caobian.info
||caobian.info
caochangqing.com
||caochangqing.com
.cap.org.hk
||cap.org.hk
.carabinasypistolas.com
cardinalkungfoundation.org
||posts.careerengine.us
carmotorshow.com
||carrd.co
ss.carryzhou.com
.cartoonmovement.com
||cartoonmovement.com
.casadeltibetbcn.org
.casatibet.org.mx
|http://casatibet.org.mx
.cari.com.my
||cari.com.my
||caribbeancom.com
.casinoking.com
.casinoriva.com
||catch22.net
.catchgod.com
|http://catchgod.com
||catfightpayperview.xxx
.catholic.org.hk
||catholic.org.hk
catholic.org.tw
||catholic.org.tw
.cathvoice.org.tw
||cato.org
||cattt.com
.cbc.ca
||cbc.ca
.cbsnews.com/video
.cbtc.org.hk
||southpark.cc.com
!-.ccc.de
!-||ccc.de
||cccat.cc
||cccat.co
.ccdtr.org
||ccdtr.org
.cchere.com
||cchere.com
.ccim.org
.cclife.ca
cclife.org
||cclife.org
cclifefl.org
||cclifefl.org
.ccthere.com
||ccthere.com
||ccthere.net
.cctmweb.net
.cctongbao.com/article/2078732
ccue.ca
ccue.com
.ccvoice.ca
.ccw.org.tw
.cgdepot.org
|http://cgdepot.org
||cdbook.org
.cdcparty.com
.cdef.org
||cdef.org
||cdig.info
cdjp.org
||cdjp.org
!--.cdn-apple.com
!--||cdn-apple.com
.cdnews.com.tw
cdp1989.org
cdp1998.org
||cdp1998.org
cdp2006.org
||cdp2006.org
.cdpa.url.tw
||cdpeu.org
||cdpuk.co.uk
||cdpusa.org
||cdpweb.org
||cdpweb.org
||cdpwu.org
||cdw.com
||cecc.gov
||cellulo.info
||cenews.eu
||centerforhumanreprod.com
||centralnation.com
.centurys.net
|http://centurys.net
.cfhks.org.hk
.cfos.de
||cfr.org
.cftfc.com
.cgst.edu
.change.org
||change.org
.changp.com
||changp.com
.changsa.net
|http://changsa.net
||channelnewsasia.com
.chapm25.com
||chatgpt.com
.chaturbate.com
||chaturbate.com
.chuang-yen.org
||checkgfw.com
chengmingmag.com
.chenguangcheng.com
||chenguangcheng.com
.chenpokong.com
||chenpokong.com
.chenpokong.net
|http://chenpokong.net
||chenpokongvip.com
||cherrysave.com
.chhongbi.org
chicagoncmtv.com
|http://chicagoncmtv.com
.china-week.com
china101.com
||china101.com
||china18.org
||china21.com
china21.org
||china21.org
.china5000.us
chinaaffairs.org
||chinaaffairs.org
||chinaaid.me
chinaaid.us
chinaaid.org
chinaaid.net
||chinaaid.net
chinacomments.org
||chinacomments.org
.chinachange.org
||chinachange.org
chinachannel.hk
||chinachannel.hk
.chinacitynews.be
.chinadialogue.net
.chinadigitaltimes.net
||chinadigitaltimes.net
.chinaelections.org
||chinaelections.org
.chinaeweekly.com
||chinaeweekly.com
||chinafile.com
||chinafreepress.org
.chinagate.com
chinageeks.org
chinagfw.org
||chinagfw.org
.chinagonet.com
.chinagreenparty.org
||chinagreenparty.org
.chinahorizon.org
||chinahorizon.org
.chinahush.com
.chinainperspective.com
||chinainterimgov.org
chinalaborwatch.org
chinalawtranslate.com
.chinapost.com.tw/taiwan/national/national-news
chinaxchina.com/howto
chinalawandpolicy.com
.chinamule.com
||chinamule.com
chinamz.org
.chinanewscenter.com
|https://chinanewscenter.com
.chinapress.com.my
||chinapress.com.my
.china-review.com.ua
|http://china-review.com.ua
.chinarightsia.org
chinasmile.net/forums
chinasocialdemocraticparty.com
||chinasocialdemocraticparty.com
chinasoul.org
||chinasoul.org
.chinasucks.net
||chinatopsex.com
.chinatown.com.au
chinatweeps.com
chinaway.org
.chinaworker.info
||chinaworker.info
chinayouth.org.hk
chinayuanmin.org
||chinayuanmin.org
.chinese-hermit.net
chinese-leaders.org
chinese-memorial.org
.chinesedaily.com
||chinesedailynews.com
.chinesedemocracy.com
||chinesedemocracy.com
||chinesegay.org
.chinesen.de
||chinesen.de
.chinesenews.net.au/
.chinesepen.org
||chineseradioseattle.com
.chinesetalks.net/ch
||chineseupress.com
.chingcheong.com
||chingcheong.com
.chinman.net
|http://chinman.net
chithu.org
||cnnews.chosun.com
.chrdnet.com
|http://chrdnet.com
.christianfreedom.org
||christianfreedom.org
christianstudy.com
||christianstudy.com
christusrex.org/www1/sdc
.chubold.com
chubun.com
||christiantimes.org.hk
.chrlawyers.hk
||chrlawyers.hk
.churchinhongkong.org/b5/index.php
|http://churchinhongkong.org/b5/index.php
.chushigangdrug.ch
.cienen.com
.cineastentreff.de
.cipfg.org
||circlethebayfortibet.org
||cirosantilli.com
.citizencn.com
||citizencn.com
||citizenlab.ca
||citizenlab.org
||citizenscommission.hk
.citizenlab.org
citizensradio.org
.city365.ca
|http://city365.ca
city9x.com
||citypopulation.de
.citytalk.tw/event
.civicparty.hk
||civicparty.hk
.civildisobediencemovement.org
civilhrfront.org
||civilhrfront.org
.civiliangunner.com
.civilmedia.tw
||civilmedia.tw
psiphon.civisec.org
||civitai.com
.ck101.com
||ck101.com
.clarionproject.org/news/islamic-state-isis-isil-propaganda
||classicalguitarblog.net
.clb.org.hk
clearharmony.net
clearwisdom.net
||clinica-tibet.ru
.clipfish.de
cloakpoint.com
||app.cloudcone.com
||cloudflare-ipfs.com
||club1069.com
||clubhouseapi.com
||cmegroup.com
||cmi.org.tw
|http://www.cmoinc.org
cmp.hku.hk
hkupop.hku.hk
||cmule.com
||cmule.org
||cms.gov
|http://vpn.cmu.edu
|http://vpn.sv.cmu.edu
.cn6.eu
.cna.com.tw
||cna.com.tw
.cnabc.com
.cnd.org
||cnd.org
download.cnet.com
.cnex.org.cn
.cnineu.com
wiki.cnitter.com
.cnn.com/video
.cnpolitics.org
||cnpolitics.org
.cn-proxy.com
|http://cn-proxy.com
.cnproxy.com
blog.cnyes.com
news.cnyes.com
||coat.co.jp
.cochina.co
||cochina.co
||cochina.org
.code1984.com/64
|http://goagent.codeplex.com
||codeshare.io
||codeskulptor.org
||conoha.jp
|http://tosh.comedycentral.com
comefromchina.com
||comefromchina.com
.comic-mega.me
commandarms.com
||commentshk.com
.communistcrimes.org
||communistcrimes.org
||communitychoicecu.com
||comparitech.com
||compileheart.com
||conoha.jp
.contactmagazine.net
.convio.net
.coobay.com
||cool18.com
.coolaler.com
||coolaler.com
coolder.com
||coolder.com
||coolloud.org.tw
.coolncute.com
||coolstuffinc.com
corumcollege.com
.cos-moe.com
|http://cos-moe.com
.cosplayjav.pl
|http://cosplayjav.pl
.cotweet.com
||cotweet.com
.coursehero.com
||coursehero.com
cpj.org
||cpj.org
.cq99.us
|http://cq99.us
crackle.com
||crackle.com
.crazys.cc
.crazyshit.com
||crazyshit.com
||crchina.org
crd-net.org
creaders.net
||creaders.net
.creadersnet.com
||cristyli.com
||croxyproxy.com
.crocotube.com
|http://crocotube.com
.crossthewall.net
||crossthewall.net
.crossvpn.net
||crossvpn.net
||crucial.com
||blog.cryptographyengineering.com
csdparty.com
||csdparty.com
||csis.org
||csmonitor.com
||csuchen.de
.csw.org.uk
.ct.org.tw
||ct.org.tw
.ctao.org
.ctfriend.net
.ctitv.com.tw
||ctowc.org
.cts.com.tw
||cts.com.tw
||ctwant.com
|http://library.usc.cuhk.edu.hk/
|http://mjlsh.usc.cuhk.edu.hk/
.cuhkacs.org/~benng
.cuihua.org
||cuihua.org
.cuiweiping.net
||cuiweiping.net
||culture.tw
.cumlouder.com
||cumlouder.com
||curvefish.com
||cusp.hk
.cusu.hk
||cusu.hk
.cutscenes.net
||cutscenes.net
.cw.com.tw
||cw.com.tw
|http://forum.cyberctm.com
cyberghostvpn.com
||cyberghostvpn.com
||cynscribe.com
cytode.us
||ifan.cz.cc
||mike.cz.cc
||nic.cz.cc
!--------------------DD-------------------------
.d-fukyu.com
|http://d-fukyu.com
cl.d0z.net
.d100.net
||d100.net
.d2bay.com
|http://d2bay.com
.dabr.co.uk
||dabr.co.uk
dabr.eu
dabr.mobi
||dabr.mobi
||dabr.me
dadazim.com
||dadazim.com
.dadi360.com
.dafabet.com
dafagood.com
dafahao.com
.dafoh.org
.daftporn.com
.dagelijksestandaard.nl
.daidostup.ru
|http://daidostup.ru
.dailidaili.com
||dailidaili.com
||dailymail.co.uk
.dailymotion.com
||dailymotion.com
||dailysabah.com
daiphapinfo.net
.dajiyuan.com
||dajiyuan.de
dajiyuan.eu
dalailama.com
.dalailama.mn
|http://dalailama.mn
.dalailama.ru
||dalailama.ru
dalailama80.org
.dalailama-archives.org
.dalailamacenter.org
|http://dalailamacenter.org
dalailamafellows.org
.dalailamafilm.com
.dalailamafoundation.org
.dalailamahindi.com
.dalailamainaustralia.org
.dalailamajapanese.com
.dalailamaprotesters.info
.dalailamaquotes.org
.dalailamatrust.org
.dalailamavisit.org.nz
.dalailamaworld.com
||dalailamaworld.com
dalianmeng.org
||dalianmeng.org
.daliulian.org
||daliulian.org
.danke4china.net
||danke4china.net
daolan.net
darktoy.net
||darrenliuwei.com
||dastrassi.org
||daum.net
.david-kilgour.com
|http://david-kilgour.com
daxa.cn
||daxa.cn
cn.dayabook.com
.daylife.com/topic/dalai_lama
||db.tt
||dbgjd.com
||dcard.tw
dcmilitary.com
||ddc.com.tw
.ddhw.info
||de-sci.org
.de-sci.org
||deadhouse.org
||deadline.com
||deepai.org
||decodet.co
!--Origin:cdn-i30$_
!--Exception: Homepage access without rst
!--Keyword is $_
.definebabe.com
||delcamp.net
delicious.com/GFWbookmark
.democrats.org
||democrats.org
.demosisto.hk
||demosisto.hk
||desc.se
||dessci.com
.destroy-china.jp
||deutsche-welle.de
||deviantart.com
||deviantart.net
||devio.us
||devpn.com
||devv.ai
||dfas.mil
dfn.org
dharmakara.net
.dharamsalanet.com
.diaoyuislands.org
||diaoyuislands.org
.difangwenge.org
|http://digiland.tw/
||digitalnomadsproject.org
.diigo.com
||diigo.com
||dilber.se
||furl.net
.dipity.com
||directcreative.com
!--||discogs.com
!--@@||cdn.discogs.com
.discuss.com.hk
||discuss.com.hk
.discuss4u.com
disp.cc
.disqus.com
||disqus.com
.dit-inc.us
||dit-inc.us
.dizhidizhi.com
||dizhuzhishang.com
djangosnippets.org
.djorz.com
||djorz.com
||dl-laby.jp
||dlive.tv
||dlsite.com
||dlyoutube.com
||dmc.nico
||dmcdn.net
.dnscrypt.org
||dnscrypt.org
||dns2go.com
||dnssec.net
doctorvoice.org
!--DogFartNetwork
.dogfartnetwork.com/tour
gloryhole.com
.dojin.com
.dok-forum.net
||dolc.de
||dolf.org.hk
||dollf.com
.domain.club.tw
.domaintoday.com.au
chinese.donga.com
dongtaiwang.com
||dongtaiwang.com
.dongtaiwang.net
||dongtaiwang.net
.dongyangjing.com
|http://danbooru.donmai.us
.dontfilter.us
||dontmovetochina.com
.dorjeshugden.com
.dotplane.com
||dotplane.com
||dotsub.com
.dotvpn.com
||dotvpn.com
.doub.io
||doub.io
||doublethinklab.org
||dougscripts.com
||douhokanko.net
||doujincafe.com
dowei.org
|https://bartender.dowjones.com
dphk.org
dpp.org.tw
||dpp.org.tw
||dpr.info
||dragonsprings.org
!--||draw.io
.dreamamateurs.com
.drepung.org
||drgan.net
.drmingxia.org
|http://drmingxia.org
||dropbooks.tv
||dropbox.com
||api.dropboxapi.com
||notify.dropboxapi.com
||dropboxusercontent.com
drsunacademy.com
.drtuber.com
.dscn.info
|http://dscn.info
.dstk.dk
|http://dstk.dk
||dtiblog.com
||dtic.mil
.dtwang.org
.duanzhihu.com
.duckdns.org
|http://duckdns.org
.duckduckgo.com
||duckduckgo.com
.duckload.com/download
||duckmylife.com
.duga.jp
|http://duga.jp
.duihua.org
||duihua.org
||duihuahrjournal.org
.dunyabulteni.net
.duoweitimes.com
||duoweitimes.com
duping.net
||duplicati.com
dupola.com
dupola.net
.dushi.ca
||duyaoss.com
||dvorak.org
.dw.com
||dw.com
||dw.de
.dw-world.com
||dw-world.com
.dw-world.de
|http://dw-world.de
www.dwheeler.com
dwnews.com
||dwnews.com
dwnews.net
||dwnews.net
xys.dxiong.com
||dynawebinc.com
||dysfz.cc
.dzze.com
!--------------------EE-------------------------
||e-classical.com.tw
||e-gold.com
.e-gold.com
.e-hentai.org
||e-hentai.org
.e-hentaidb.com
|http://e-hentaidb.com
e-info.org.tw
.e-traderland.net/board
.e-zone.com.hk/discuz
|http://e-zone.com.hk/discuz
.e123.hk
||e123.hk
.earlytibet.com
|http://earlytibet.com
.earthcam.com
.earthvpn.com
||earthvpn.com
eastern-ark.com
.easternlightning.org
.eastturkestan.com
|http://www.eastturkistan.net/
.eastturkistan-gov.org
.eastturkistancc.org
.eastturkistangovernmentinexile.us
||eastturkistangovernmentinexile.us
.easyca.ca
.easypic.com
||fnc.ebc.net.tw
||news.ebc.net.tw
.ebony-beauty.com
ebookbrowse.com
ebookee.com
||ecfa.org.tw
ushuarencity.echainhost.com
||ecimg.tw
ecministry.net
.economist.com
bbs.ecstart.com
edgecastcdn.net
||edgecastcdn.net
/twimg\.edgesuite\.net\/\/?appledaily/
edicypages.com
.edmontonchina.cn
.edmontonservice.com
edoors.com
.edubridge.com
||edubridge.com
.edupro.org
||eevpn.com
efcc.org.hk
.efukt.com
|http://efukt.com
||eic-av.com
||eireinikotaerukai.com
.eisbb.com
.eksisozluk.com
||eksisozluk.com
electionsmeter.com
||elgoog.im
.ellawine.org
.elpais.com
||elpais.com
.eltondisney.com
.emaga.com/info/3407
emilylau.org.hk
.emanna.com/chineseTraditional
bitc.bme.emory.edu/~lzhou/blogs
.empfil.com
.emule-ed2k.com
|http://emule-ed2k.com
.emulefans.com
|http://emulefans.com
.emuparadise.me
.enanyang.my
!--.enanyang.my/news/20170502/%E7%BE%8E%E5%9B%BD%E4%B9%8B%E9%9F%B3%E5%A4%A7%E5%9C%B0%E9%9C%87%E3%80%8A%E8%8B%B9%E6%9E%9C%E3%80%8B%E7%8B%AC%E5%AE%B6
||encrypt.me
||enewstree.com
.enfal.de
||chinese.engadget.com
||engagedaily.org
englishforeveryone.org
||englishfromengland.co.uk
englishpen.org
.enlighten.org.tw
||entermap.com
||app.evozi.com
.episcopalchurch.org
.epochhk.com
||epochhk.com
epochtimes-bg.com
||epochtimes-bg.com
epochtimes-romania.com
||epochtimes-romania.com
epochtimes.co.il
||epochtimes.co.il
epochtimes.co.kr
||epochtimes.co.kr
epochtimes.com
||epochtimes.com
.epochtimes.cz
||epochtimes.de
||epochtimes.fr
||epochtimes.ie
||epochtimes.it
||epochtimes.jp
||epochtimes.ru
||epochtimes.se
||epochtimestr.com
.epochweek.com
||epochweek.com
||epochweekly.com
.eporner.com
.equinenow.com
erabaru.net
.eracom.com.tw
.eraysoft.com.tr
.erepublik.com
.erights.net
||erights.net
.erktv.com
|http://erktv.com
||ernestmandel.org
||erodaizensyu.com
||erodoujinlog.com
||erodoujinworld.com
||eromanga-kingdom.com
||eromangadouzin.com
.eromon.net
|http://eromon.net
.eroprofile.com
.eroticsaloon.net
.eslite.com
||eslite.com
!--.eslite.com/product
!--.eslite.com/Search_BW.aspx?q
wiki.esu.im/%E8%9B%A4%E8%9B%A4%E8%AF%AD%E5%BD%95
||esu.dog
.etaa.org.au
.etadult.com
etaiwannews.com
||etizer.org
||etokki.com
||etsy.com
!--.ettoday.net
.ettoday.net/news/20151216/614081
etvonline.hk
.eu.org
||eu.org
.eucasino.com
.eulam.com
.eurekavpt.com
||eurekavpt.com
.euronews.com
||euronews.com
eeas.europa.eu/delegations/china/press_corner/all_news/news/2015/20150716_zh
eeas.europa.eu/statements-eeas/2015/151022
||apps.evozi.com
||evschool.net
||exblog.jp
||blog.exblog.co.jp
@@||www.exblog.jp
.exchristian.hk
||exchristian.hk
|http://blog.excite.co.jp
||exhentai.org
||exmormon.org
||expatshield.com
.expecthim.com
||expecthim.com
experts-univers.com
||exploader.net
.expressvpn.com
||expressvpn.com
.extremetube.com
eyevio.jp
||eyevio.jp
.eyny.com
||eyny.com
.ezpc.tk/category/soft
.ezpeer.com
!--------------------FF-------------------------
||facebookquotes4u.com
.faceless.me
||faceless.me
|http://facesoftibetanselfimmolators.info
||facesofnyfw.com
||factpedia.org
.faith100.org
|http://faith100.org
!--Enhancement:
!--http://faithfuleye.com.detail.website/
!--http://faithfuleye.com.ipaddress.com/
.faithfuleye.com
||faiththedog.info
.fakku.net
||fallenark.com
.falsefire.com
||falsefire.com
falun-co.org
falunart.org
||falunasia.info
|http://falunau.org
.falunaz.net
falundafa.org
falundafa-dc.org
||falundafa-florida.org
||falundafa-nc.org
||falundafa-pa.net
||falundafa-sacramento.org
falun-ny.net
||falundafaindia.org
falundafamuseum.org
.falungong.club
.falungong.de
falungong.org.uk
||falunhr.org
faluninfo.de
faluninfo.net
.falunpilipinas.net
||falunworld.net
familyfed.org
.fangeming.com
||fanglizhi.info
||fangong.org
fangongheike.com
||fanhaolou.com
.fanqiang.tk
fanqianghou.com
||fanqianghou.com
.fanqiangzhe.com
||fanqiangzhe.com
||fantv.hk
fapdu.com
faproxy.com
!--.farxian.com
.fawanghuihui.org
fanqiangyakexi.net
fail.hk
||famunion.com
.fan-qiang.com
.fangbinxing.com
||fangbinxing.com
fangeming.com
.fangmincn.org
||fangmincn.org
.fanhaodang.com
||fanqiang.network
||fanswong.com
.fanyue.info
.farwestchina.com
!--Fastly
en.favotter.net
!--||rnw.global.ssl.fastly.net
!--|https://*global.ssl.fastly.net/
nytimes.map.fastly.net
||nytimes.map.fastly.net
||fast.wistia.com
||fastestvpn.com
||fastssh.com
||faststone.org
favstar.fm
||favstar.fm
faydao.com/weblog
||faz.net
.fc2.com
.fc2china.com
.fc2cn.com
||fc2cn.com
fc2blog.net
|http://uygur.fc2web.com/
video.fdbox.com
.fdc64.de
.fdc64.org
.fdc89.jp
||fourface.nodesnoop.com
!--feedbooks.mobi
||feeder.co
||feelssh.com
feer.com
.feifeiss.com
|http://feitianacademy.org
.feitian-california.org
||feixiaohao.com
||feministteacher.com
.fengzhenghu.com
||fengzhenghu.com
.fengzhenghu.net
||fengzhenghu.net
.fevernet.com
|http://ff.im
fffff.at
fflick.com
.ffvpn.com
fgmtv.net
.fgmtv.org
.fhreports.net
|http://fhreports.net
.figprayer.com
||figprayer.com
.fileflyer.com
||fileflyer.com
|http://feeds.fileforum.com
.files2me.com
.fileserve.com/file
fillthesquare.org
filmingfortibet.org
.filthdump.com
.finchvpn.com
||finchvpn.com
!--findbook.tw
findmespot.com
||findyoutube.com
||findyoutube.net
.fingerdaily.com
finler.net
.firearmsworld.net
|http://firearmsworld.net
||relay.firefox.com
.fireofliberty.org
||fireofliberty.org
.firetweet.io
||firetweet.io
||firstpost.com
||firstrade.com
||fish.audio
!--||flagfox.net
.flagsonline.it
fleshbot.com
.fleursdeslettres.com
|http://fleursdeslettres.com
||flgg.us
||flgjustice.org
!--||farm6.staticflickr.com
!--.flickr.com/photos/46231077@N06
!--.flickr.com/groups/aiweiwei
!--.flickr.com/photos/digitalboy100
!--.flickr.com/photos/fzhenghu
!--.flickr.com/photos/lonelyfox
!--flickr.com/photos/vanvan/529925157
!--.flickr.com/photos/winterkanal
!--.flickr.com/photos/zola
||flickr.com
||staticflickr.com
flickrhivemind.net
.flickriver.com
.fling.com
||flipkart.com
||flog.tw
.flyvpn.com
||flyvpn.com
|http://cn.fmnnow.com
fofldfradio.org
blog.foolsmountain.com
.forum4hk.com
fangong.forums-free.com
pioneer-worker.forums-free.com
!--foursquare.com
!--|http://4sq.com
|https://ss*.4sqi.net
video.foxbusiness.com
|http://foxgay.com
||fringenetwork.com
||flecheinthepeche.fr
.fochk.org
||fochk.org
||focustaiwan.tw
.focusvpn.com
||fofg.org
.fofg-europe.net
.fooooo.com
||fooooo.com
||foreignaffairs.com
.fotile.me
||fourthinternational.org
||foxdie.us
||foxsub.com
foxtang.com
.fpmt.org
|http://fpmt.org
.fpmt.tw
.fpmt-osel.org
||fpmtmexico.org
fqok.org
||fqrouter.com
||franklc.com
.freakshare.com
|http://freakshare.com
||free4u.com.ar
free-gate.org
.free-hada-now.org
free-proxy.cz
.free.fr/adsl
kineox.free.fr
tibetlibre.free.fr
||freealim.com
whitebear.freebearblog.org
||freebrowser.org
.freechal.com
.freedomchina.info
||freedomchina.info
.freedomhouse.org
||freedomhouse.org
.freedomsherald.org
||freedomsherald.org
.freefq.com
.freefuckvids.com
.freegao.com
||freegao.com
freeilhamtohti.org
||freekazakhs.org
.freekwonpyong.org
||saveliuxiaobo.com
.freelotto.com
||freelotto.com
freeman2.com
.freeopenvpn.com
freemoren.com
freemorenews.com
freemuse.org/archives/789
freenet-china.org
freenewscn.com
cn.freeones.com
.freeoz.org/bbs
||freeoz.org
||freessh.us
free4u.com.ar
.free-ssh.com
||free-ssh.com
||freebeacon.com
.freechina.news
||freechinaforum.org
||freechinaweibo.com
.freedomcollection.org/interviews/rebiya_kadeer
.freeforums.org
||freenetproject.org
.freeoz.org
.freetibet.net
||freetibet.org
.freetibetanheroes.org
|http://freetibetanheroes.org
||freetribe.me
.freeviewmovies.com
.freevpn.me
|http://freevpn.me
||freewallpaper4.me
.freewebs.com
.freewechat.com
||freewechat.com
freeweibo.com
||freeweibo.com
.freexinwen.com
.freeyoutubeproxy.net
||freeyoutubeproxy.net
friendfeed.com
friendfeed-media.com/e99a4ebe2fb4c1985c2a58775eb4422961aa5a2e
friends-of-tibet.org
.friendsoftibet.org
||friendsoftibet.org
freechina.net
|http://www.zensur.freerk.com/
freevpn.nl
freeyellow.com
hk.frienddy.com/hk
|http://adult.friendfinder.com/
.fring.com
||fring.com
.fromchinatousa.net
||frommel.net
.frontlinedefenders.org
||frontlinedefenders.org
.frootvpn.com
||frootvpn.com
||fscked.org
.fsurf.com
.ftv.com.tw
||ftv.com.tw
||ftvnews.com.tw
fucd.com
.fuckcnnic.net
||fuckcnnic.net
fuckgfw.org
.fulione.com
|https://fulione.com
||fullerconsideration.com
fulue.com
.funf.tw
funp.com
.fuq.com
.furhhdl.org
||furinkan.com
.futurechinaforum.org
||futuremessage.org
.fux.com
.fuyin.net
.fuyindiantai.org
.fuyu.org.tw
||fw.cm
.fxcm-chinese.com
||fxcm-chinese.com
fzh999.com
fzh999.net
fzlm.com
!--------------------GG-------------------------
.g6hentai.com
|http://g6hentai.com
||g-queen.com
||gab.com
||gabocorp.com
.gaeproxy.com
.gaforum.org
.gagaoolala.com
||gagaoolala.com
.galaxymacau.com
||galenwu.com
.galstars.net
||game735.com
gamebase.com.tw
gamejolt.com
|http://wiki.gamerp.jp
||gamer.com.tw
.gamer.com.tw
.gamez.com.tw
||gamez.com.tw
.gamousa.com
.gaoming.net
||gaoming.net
ganges.com
||ganjing.com
||ganjingworld.com
.gaopi.net
|http://gaopi.net
.gaozhisheng.org
.gaozhisheng.net
gardennetworks.com
||gardennetworks.org
!--IP of Garden Network
72.52.81.22
||gartlive.com
||gate-project.com
||gather.com
.gatherproxy.com
gati.org.tw
.gaybubble.com
.gaycn.net
.gayhub.com
||gaymap.cc
.gaymenring.com
.gaytube.com
!--||gaytube.com
||images-gaytube.com
.gaywatch.com
|http://gaywatch.com
.gazotube.com
||gazotube.com
||gcc.org.hk
||gclooney.com
||gclubs.com
||gcmasia.com
.gcpnews.com
|http://gcpnews.com
.gdbt.net/forum
gdzf.org
||geek-art.net
geekerhome.com/2010/03/xixiang-project-cross-gfw
||geekheart.info
.gekikame.com
|http://gekikame.com
.gelbooru.com
|http://gelbooru.com
||generated.photos
||genius.com
!--||genuitec.com
.geocities.co.jp
.geocities.com/SiliconValley/Circuit/5683/download.html
hk.geocities.com
geocities.jp
||geph.io
.gerefoundation.org
||getastrill.com
.getchu.com
.getcloak.com
||getcloak.com
||getfoxyproxy.org
.getfreedur.com
||getgom.com
.geti2p.net
||geti2p.net
getiton.com
.getjetso.com/forum
.getlantern.org
||getlantern.org
||getmalus.com
.getsocialscope.com
||getsync.com
||gettr.com
gfbv.de
.gfgold.com.hk
.gfsale.com
||gfsale.com
gfw.org.ua
.gfw.press
||gfw.press
||gfw.report
.ggssl.com
||ggssl.com
!--||ghost.org
.ghostpath.com
||ghostpath.com
||ghut.org
.giantessnight.com
|http://giantessnight.com
.gifree.com
||giga-web.jp
tw.gigacircle.com
|http://cn.giganews.com/
gigporno.ru
||girlbanker.com
.git.io
||git.io
|http://softwaredownload.gitbooks.io
||raw.githack.com
!---GitHub---
||github.blog
||github.com
!--github.com/getlantern
!--|https://gist.github.com
!--http://cthlo.github.io/hktv
!--hahaxixi.github.io
!--|https://hahaxixi.github.io
!--||haoel.github.io
!--|http://onionhacker.github.io
!--||rg3.github.io
!--||sikaozhe1997.github.io
!--||sodatea.github.io
!--||terminus2049.github.io
!--||toutyrater.github.io
!--wsgzao.github.io
!--|https://wsgzao.github.io
.github.io
||github.io
||githubusercontent.com
||githubassets.com
.gizlen.net
||gizlen.net
.gjczz.com
||gjczz.com
globaljihad.net
globalmediaoutreach.com
globalmuseumoncommunism.org
||globalrescue.net
.globaltm.org
.globalvoicesonline.org
||globalvoicesonline.org
||globalvpn.net
.glock.com
gluckman.com/DalaiLama
||gmgard.com
||gmhz.org
|http://www.gmiddle.com
|http://www.gmiddle.net
.gmll.org
||suche.gmx.net
||gnci.org.hk
||gnews.org
go-pki.com
||goagent.biz
||goagentplus.com
gobet.cc
||godaddy.com
godfootsteps.org
||godfootsteps.org
godns.work
godsdirectcontact.co.uk
.godsdirectcontact.org
godsdirectcontact.org.tw
.godsimmediatecontact.com
||gofundme.com
.gogotunnel.com
||gohappy.com.tw
.gokbayrak.com
.goldbet.com
||goldbetsports.com
||golden-ages.org
||goldeneyevault.com
.goldenfrog.com
||goldenfrog.com
.goldjizz.com
|http://goldjizz.com
.goldstep.net
||goldwave.com
gongmeng.info
gongm.in
gongminliliang.com
.gongwt.com
|http://gongwt.com
blog.goo.ne.jp/duck-tail_2009
.gooday.xyz
||gooday.xyz
||goodhope.school
.goodreads.com
||goodreads.com
.goodreaders.com
||goodreaders.com
.goodtv.com.tw
.goodtv.tv
||goofind.com
.googlesile.com
.gopetition.com
||gopetition.com
.goproxing.net
||goreforum.com
.gotrusted.com
||gotrusted.com
||gotw.ca
||grammaly.com
grandtrial.org
.graphis.ne.jp
||graphis.ne.jp
||graphql.org
||gravatar.com
greatfirewall.biz
||greatfirewallofchina.net
.greatfirewallofchina.org
||greatfirewallofchina.org
||greenfieldbookstore.com.hk
.greenparty.org.tw
||greenpeace.org
.greenreadings.com/forum
great-firewall.com
great-roc.org
greatroc.org
greatzhonghua.org
.greenpeace.com.tw
.greenvpn.net
||greenvpn.net
.greenvpn.org
||grindr.com
||ground.news
||grotty-monday.com
gs-discuss.com
||gsearch.media
||gtricks.com
guancha.org
guaneryu.com
.guardster.com
.gun-world.net
gunsandammo.com
||gutteruncensored.com
||gvm.com.tw
||gwins.org
.gzm.tv
||gzone-anime.info
!-------------GHS-----
!-||feeds.cbsnews.com
!-||www.chinesealbumart.com
||clementine-player.org
!-||clemesha.org
!-||www.cloudgirlfriend.com
!-||cocoawithlove.com
!-||blog.controlspace.org
!-D
!-||www.dailygyan.com
!-||dailytodo.org
!-||blog.danmarner.com
!-||github.danmarner.com
!-||design-seeds.com
!-||designers-artists.com
!-||mail.diyang.org
!-||blog.doughellmann.com
!-||downforeveryoneorjustme.com
!-||droidsecurity.com
!-||www.dropmocks.com
!-||dumblittleman.com
!-E
echofon.com
!-||echofon.com
!-||epc-jav.com
!-||everdark.info
!-||evhead.com
!-F
!-||facilelogin.com
!-||*.fatduck.org
!-||blog.fdcn.org
!-||fftogo.com
!-||flightsimtalk.com
!-||mclee.foolme.net
!-||www.frienddeck.com
!-||fringespoilers.com
!-||fringetelevision.com
!-||funpea.com
!-G
!-||blog.gatein.org
!-||feeds.gawker.com
!-||geektang.com
!-||geohot.us
!-||getaround.com
!-||gmer.net
!-||www.gmote.org
!-||blog.go2web20.net
!-||google-melange.com
!-||fame.gonzolabs.org
!-||govecn.org
!-||gqueues.com
!-||graphycalc.com
!-||blog.growlforwindows.com
!-H
!-||hcm.com.tw
!-||blog.headius.com
!-||hogbaysoftware.com
!-||blog.hotot.org
!-||feeds.howstuffworks.com
!-||huhaitai.com
!-||blog.humanrightsfirst.org
!-I
!-||site.icu-project.org
!-||igorware.com
!-||ihas1337code.com
!-||inknouveau.com
!-||inote.tw
!-||ironhelmet.com
!-||iwfwcf.com
!-J
!-||blog.jangmt.com
!-||blog.jayfields.com
!-||blog.joint.net
!-||blog.jsquaredjavascript.com
!-||blog.jtbworld.com
!-K
!-||kathyschwalbe.com
!-||tomatovpn.keithmoyer.com
!-||www.keithmoyer.com
!-||kendalvandyke.com
!-||blog.kengao.tw
!-||log.keso.cn
!-||www.khanacademy.org
||www.klip.me
!-||usbloadergx.koureio.net
!-||blog.kowalczyk.info
!-L
!-||labyrinth2.com
!-||larsgeorge.com
!-||blog.lastpass.com
!-||docs.latexlab.org
!-||leanessays.com
!-||blog.lidaobing.info
!-||log.lightory.net
!-||feeds.limi.net
!-||www.liteapplications.com
!-||blog.liukangxu.info
!-||twitter.liukangxu.info
!-||oasisnewsroom.live4ever.us
!-||www.lockergnome.com
!-||locql.com
@@||site.locql.com
!-||feeds.loiclemeur.com
!-||blog.louisgray.com
!-M
!-||madebysofa.com
!-||mademoisellerobot.com
!-||masamixes.com
!-||www.metamuse.net
!-||blog.metasploit.com
!-||milazi.com
!-||www.miniweather.com
!-||twitter.missiu.com
!-||plurktop-button.mmdays.com
!-||feeds.mobileread.com
!-||www.modernizr.com
!-||www.modk.it
!-||mytwishirt.com
!-N
!-||blog.netflix.com
!-||blog.nihilogic.dk
!-||ntlk.org
!-||nvquan.org
!-||nogoodatcoding.com
!-||blog.notdot.net
!-||www.notify.io
!-O
!-||blog.obvious.com
!-||onebigfluke.com
!-||overstimulate.com
!-P
!-||pcgeekblog.com
!-||feeds.pdfchm.net
!-||feeds.people.com
!-||blog.persistent.info
!-||chrome.plantsvszombies.com
!-||portablesoft.org.ru
!-||prasannatech.net
!-||talk.news.pts.org.tw
!-||python-excel.org
!-Q
!-R
!-||r-chart.com
!-||rameshsubramanian.org
!-||rapid.pk
!-||blog.renanse.com
!-||robertmao.com
!-||www.romeo-foxtrot.com
!-S
!-||salmiyuck.com
!-||samsal.com
!-||blog.seeminglee.com
!-||blog.sflow.com
!-||blog.sigfpe.com
!-||simpletext.ws
!-||www.skulpt.org
!-||rss.slashdot.org
!-||snippetsapp.com
!-||w.sns.ly
!-||www.socialnmobile.com
!-||www.socialwhois.com
!-||spiritjb.org
!-||ssbook.com
!-||sshforwarding.com
!-||stationeria.com
||stephaniered.com
!-||sunjidong.net
!-||syniumsoftware.com
@@||download.syniumsoftware.com
!-T
!-||tagxedo.com
!-||blog.tatoeba.org
!-||www.techfob.com
!-||teachparentstech.org
!-||the8pen.com
!-||theiphonewiki.com
!-||blog.thesilentnumber.me
!-||thesponty.com
!-||theultralinx.com
!-||blog.think-async.com
!-||tornadoweb.org
!-||transparentuptime.com
!-||triangulationblog.com
!-||blog.tsunanet.net
!-||en.tuxero.com
!-||twazzup.com
!-||tweetswell.com
!-||twibes.com
!-||art.twgg.org
!-||twivert.com
!-U
|http://ub0.cc
!-||jonny.ubuntu-tw.net
!-||blog.umonkey.net
!-V
!-||tp.vbap.com.au
!-||www.virtuousrom.com
!-||blog.visibotech.com
!-W
!-||waveprotocol.org
!-||www.wavesandbox.com
!-||webfee.org.ru
!-||blog.webmproject.org
!-||webupd8.org
!-||www.whatbrowser.org
!-||www.wheredoyougo.net
!-||willhains.com
!-||feeds.wired.com
!-||wisemapping.org
wozy.in
!-||wozy.in/
!-||blog.wundercounter.com
!-X
!-||xdelta.org
!-||xiaogaozi.org
!-||xilou.us
!-||xzy.org.ru
!-Y
!-||yooper.be
!-||tsong.yunxi.net
!-Z
gospelherald.com
||gospelherald.com
|http://hk.gradconnection.com/
||grangorz.org
greatfire.org
||greatfire.org
greatfirewallofchina.org
||greatroc.tw
.gts-vpn.com
|http://gts-vpn.com
||gtv.org
||gtv1.org
.gu-chu-sum.org
|http://gu-chu-sum.org
.guaguass.com
|http://guaguass.com
.guaguass.org
|http://guaguass.org
.guangming.com.my
guishan.org
||guishan.org
.gumroad.com
||gumroad.com
||gunsamerica.com
guruonline.hk
|http://gvlib.com
.gyalwarinpoche.com
.gyatsostudio.com
!--------------------HH-------------------------
.h528.com
.h5dm.com
.h5galgame.me
||h-china.org
.h-moe.com
|http://h-moe.com
h1n1china.org
.hacken.cc/bbs
.hacker.org
||hackmd.io
||hackthatphone.net
hahlo.com
||haijiao.com
||hakkatv.org.tw
.handcraftedsoftware.org
|http://bbs.hanminzu.org/
.hanunyi.com
.hao.news/news
|http://ae.hao123.com
|http://ar.hao123.com
|http://br.hao123.com
|http://en.hao123.com
|http://id.hao123.com
|http://jp.hao123.com
|http://ma.hao123.com
|http://mx.hao123.com
|http://sa.hao123.com
|http://th.hao123.com
|http://tw.hao123.com
|http://vn.hao123.com
|http://hk.hao123img.com
|http://ld.hao123img.com
||happy-vpn.com
.haproxy.org
||hardsextube.com
.harunyahya.com
|http://harunyahya.com
bbs.hasi.wang
have8.com
@@||haygo.com
.hclips.com
||hdlt.me
||hdtvb.net
.hdzog.com
|http://hdzog.com
||ordns.he.net
||heartyit.com
.heavy-r.com
.hec.su
|http://hec.su
.hecaitou.net
||hecaitou.net
.hechaji.com
||hechaji.com
||heeact.edu.tw
.hegre-art.com
|http://hegre-art.com
||cdn.helixstudios.net
||helplinfen.com
||helpuyghursnow.org
||helloandroid.com
||helloqueer.com
.helloss.pw
hellotxt.com
||hellotxt.com
.hentai.to
.hellouk.org/forum/lofiversion
.helpeachpeople.com
||helpeachpeople.com
||helpster.de
.helpzhuling.org
hentaitube.tv
.hentaivideoworld.com
!###########--Heroku--##########
!--||getcloudapp.com
!--||cl.ly
!--@@||f.cl.ly
!--EC2 DNS Poisoned
||id.heroku.com
||herokuapp.com
||heqinglian.net
||heritage.org
||heungkongdiscuss.com
.hexieshe.com
||hexieshe.com
||hexieshe.xyz
!--Google employee within Google IP
||hexxeh.net
||heyuedi.com
app.heywire.com
.heyzo.com
.hgseav.com
.hhdcb3office.org
.hhthesakyatrizin.org
hi-on.org.tw
||hiccears.com
hidden-advent.org
||hidden-advent.org
hidecloud.com/blog/2008/07/29/fuck-beijing-olympics.html
||hide.me
.hidein.net
.hideipvpn.com
||hideipvpn.com
.hideman.net
||hideman.net
hideme.nl
||hidemy.name
.hidemyass.com
||hidemyass.com
hidemycomp.com
||hidemycomp.com
.hihiforum.com
.hihistory.net
||hihistory.net
.higfw.com
highpeakspureearth.com
||highrockmedia.com
||hiitch.com
||hikinggfw.org
.hilive.tv
.himalayan-foundation.org
||himalayan-foundation.org
himalayanglacier.com
.himemix.com
||himemix.com
.himemix.net
times.hinet.net
.hitomi.la
|http://hitomi.la
.hiwifi.com
@@||hiwifi.com
hizbuttahrir.org
hizb-ut-tahrir.info
hizb-ut-tahrir.org
.hjclub.info
.hk-pub.com/forum
|http://hk-pub.com
.hk01.com
||hk01.com
.hk32168.com
||hk32168.com
||hkacg.com
||hkacg.net
.hkatvnews.com
hkbc.net
.hkbf.org
.hkbookcity.com
||hkbookcity.com
||hkchronicles.com
.hkchurch.org
hkci.org.hk
.hkcmi.edu
||hkcnews.com
||hkcoc.com
||hkctu.org.hk
hkday.net
.hkdailynews.com.hk/china.php
||hkdc.us
hkdf.org
.hkej.com
.hkepc.com/forum/viewthread.php?tid=1153322
||hket.com
||hkfaa.com
hkfreezone.com
hkfront.org
m.hkgalden.com
|https://m.hkgalden.com
.hkgreenradio.org/home
||hkgpao.com
.hkheadline.com*blog
.hkheadline.com/instantnews
hkhkhk.com
hkhrc.org.hk
hkhrm.org.hk
||hkip.org.uk
1989report.hkja.org.hk
hkjc.com
.hkjp.org
.hklft.com
.hklts.org.hk
||hklts.org.hk
||hkmap.live
||hkopentv.com
||hkpeanut.com
hkptu.org
.hkreporter.com
||hkreporter.com
|http://hkupop.hku.hk/
.hkusu.net
||hkusu.net
.hkvwet.com
.hkwcc.org.hk
||hkzone.org
.hmonghot.com
|http://hmonghot.com
.hmv.co.jp/
hnjhj.com
||hnjhj.com
.hnntube.com
||hojemacau.com.mo
||hola.com
||hola.org
holymountaincn.com
holyspiritspeaks.org
||holyspiritspeaks.org
||derekhsu.homeip.net
.homeperversion.com
|http://homeservershow.com
|http://old.honeynet.org/scans/scan31/sub/doug_eric/spam_translation.html
.hongkongfp.com
||hongkongfp.com
hongmeimei.com
||hongzhi.li
||honven.xyz
.hootsuite.com
||hootsuite.com
||hoover.org
.hopedialogue.org
|http://hopedialogue.org
.hopto.org
.hornygamer.com
.hornytrip.com
|http://hornytrip.com
||horrorporn.com
||hostloc.com
||hotair.com
.hotav.tv
.hotels.cn
hotfrog.com.tw
hotgoo.com
.hotpornshow.com
hotpot.hk
.hotshame.com
||hotspotshield.com
||hottg.com
.hotvpn.com
||hotvpn.com
||hougaige.com
||howtoforge.com
||hoxx.com
||hpjav.com
.hqcdp.org
||hqcdp.org
||hqjapanesesex.com
hqmovies.com
.hrcir.com
.hrcchina.org
.hrea.org
.hrichina.org
||hrichina.org
||hrntt.org
.hrtsea.com
.hrw.org
||hrw.org
hrweb.org
||hsex.men
||hsjp.net
||hsselite.com
||hst.net.tw
.hstern.net
.hstt.net
.htkou.net
||htkou.net
.hua-yue.net
.huaglad.com
||huaglad.com
.huanghuagang.org
||huanghuagang.org
.huangyiyu.com
.huaren.us
||huaren.us
.huaren4us.com
.huashangnews.com
|http://huashangnews.com
bbs.huasing.org
huaxia-news.com
huaxiabao.org
huaxin.ph
||huayuworld.org
||huffingtonpost.com
||huffpost.com
||huggingface.co
||hugoroy.eu
||huhaitai.com
||huhamhire.com
.huhangfei.com
||huhangfei.com
huiyi.in
.hulkshare.com
||humanparty.me
||humanrightspressawards.org
||hung-ya.com
||hungerstrikeforaids.org
||huping.net
hurgokbayrak.com
.hurriyet.com.tr
.hut2.ru
||hutianyi.net
hutong9.net
huyandex.com
.hwadzan.tw
||hwayue.org.tw
||hwinfo.com
||hxwk.org
hxwq.org
||hyperrate.com
ebook.hyread.com.tw
||ebook.hyread.com.tw
!--------------------II-------------------------
||i1.hk
||i2p2.de
||i2runner.com
||i818hk.com
.i-cable.com
.i-part.com.tw
.iamtopone.com
iask.ca
||iask.ca
iask.bz
||iask.bz
.iav19.com
||iavian.net
ibiblio.org/pub/packages/ccic
||ibit.am
.iblist.com
||iblogserv-f.net
ibros.org
|http://cn.ibtimes.com
.ibvpn.com
||ibvpn.com
icams.com
||icedrive.net
.icij.org
||icij.org
||icl-fi.org
.icoco.com
||icoco.com
!--38.103.165.50
||furbo.org
!--||iconfactory.com
||warbler.iconfactory.net
||iconpaper.org
!-- Google Pages
||icu-project.org
w.idaiwan.com/forum
idemocracy.asia
.identi.ca
||identi.ca
||idiomconnection.com
|http://www.idlcoyote.com
.idouga.com
.idreamx.com
forum.idsam.com
.idv.tw
.ieasy5.com
|http://ieasy5.com
.ied2k.net
.ienergy1.com
||iepl.us
||ift.tt
ifanqiang.com
.ifcss.org
||ifcss.org
ifjc.org
.ift.tt
|http://ift.tt
||ifreewares.com
||igcd.net
.igfw.net
||igfw.net
.igfw.tech
||igfw.tech
.igmg.de
||ignitedetroit.net
.igotmail.com.tw
||igvita.com
||ihakka.net
.ihao.org/dz5
||iicns.com
.ikstar.com
||ilhamtohtiinstitute.org
||illusionfactory.com
||ilove80.be
||im.tv
@@||myvlog.im.tv
||im88.tw
||imgchili.net
.imageab.com
.imagefap.com
||imagefap.com
||imageflea.com
||imageglass.org
||imageshack.us
||imagevenue.com
||imagezilla.net
.imb.org
|http://imb.org
!--IMDB
|http://www.imdb.com/name/nm0482730
.imdb.com/title/tt0819354
.imdb.com/title/tt1540068
.imdb.com/title/tt4908644
.img.ly
||img.ly
||imgasd.com
.imgur.com
||imgur.com
.imkev.com
||imkev.com
.imlive.com
.immoral.jp
impact.org.au
impp.mn
|http://tech2.in.com/video/
in99.org
in-disguise.com
.incapdns.net
.incloak.com
||incloak.com
||incredibox.fr
||independent.co.uk
||indiablooms.com
||indiandefensenews.in
||indianarrative.com
||timesofindia.indiatimes.com
.indiemerch.com
||indiemerch.com
||info-graf.fr
website.informer.com
||inherit.live
||initiativesforchina.org
||inkbunny.net
||inkui.com
||inmediahk.net
||inmediahk.net
||innermongolia.org
||inoreader.com
||inote.tw
||insecam.org
|http://insecam.org
||inside.com.tw
||insidevoa.com
||institut-tibetain.org
||interactivebrokers.com
||internet.org
internetdefenseleague.org
||internetfreedom.org
!--||interpol.int
||internetpopculture.com
.inthenameofconfuciusmovie.com
||inthenameofconfuciusmovie.com
inxian.com
||inxian.com
ipalter.com
!--||ipcf.org.tw
||ipfire.org
||iphone4hongkong.com
||iphonehacks.com
||iphonetaiwan.org
||iphonix.fr
||ipicture.ru
.ipjetable.net
||ipjetable.net
.ipobar.com/read.php?
ipoock.com/img
.iportal.me
|http://iportal.me
||ippotv.com
.ipredator.se
||ipredator.se
.iptv.com.tw
||iptvbin.com
||ipvanish.com
iredmail.org
chinese.irib.ir
||ironbigfools.compython.net
||ironpython.net
.ironsocket.com
||ironsocket.com
.is.gd
.islahhaber.net
.islam.org.hk
|http://islam.org.hk
.islamawareness.net/Asia/China
.islamhouse.com
||islamhouse.com
.islamicity.com
.islamicpluralism.org
.islamtoday.net
.isaacmao.com
||isaacmao.com
||isgreat.org
||ismaelan.com
.ismalltits.com
||ismprofessional.net
isohunt.com
||israbox.com
.issuu.com
||issuu.com
.istars.co.nz
oversea.istarshine.com
||oversea.istarshine.com
blog.istef.info/2007/10/21/myentunnel
.istiqlalhewer.com
.istockphoto.com
isunaffairs.com
isuntv.com
||isupportuyghurs.org
itaboo.info
||itaboo.info
||italiatibet.org
||itemfix.com
ithelp.ithome.com.tw
||itshidden.com
.itsky.it
.itweet.net
|http://itweet.net
.iu45.com
.iuhrdf.org
||iuhrdf.org
.iuksky.com
.ivacy.com
||ivacy.com
.iverycd.com
||ivonblog.com
.ivpn.net
||ivpn.net
||iwara.tv
||ixquick.com
.ixxx.com
.iyouport.com
||iyouport.com
||iyouport.org
.izaobao.us
||gmozomg.izihost.org
.izles.net
.izlesem.org
!--------------------JJ-------------------------
||j.mp
||jable.tv
||blog.jackjia.com
jamaat.org
||jamestown.org
||jamyangnorbu.com
||jan.ai
.jandyx.com
||janwongphoto.com
||japan-whores.com
.jav.com
.jav101.com
.jav2be.com
||jav2be.com
.jav68.tv
.javakiba.org
|http://javakiba.org
.javbus.com
||javbus.com
||javfor.me
.javhd.com
.javhip.com
.javmobile.net
|http://javmobile.net
.javmoo.com
.javseen.com
|http://javseen.com
jbtalks.cc
jbtalks.com
jbtalks.my
.jdwsy.com
jeanyim.com
||jfqu36.club
||jfqu37.xyz
||jgoodies.com
.jiangweiping.com
||jiangweiping.com
||jiaoyou8.com
||jichangtj.com
.jiehua.cz
||hk.jiepang.com
||tw.jiepang.com
jieshibaobao.com
.jigglegifs.com
56cun04.jigsy.com
jigong1024.com
daodu14.jigsy.com
specxinzl.jigsy.com
wlcnew.jigsy.com
.jihadology.net
|http://jihadology.net
jinbushe.org
||jinbushe.org
.jingsim.org
zhao.jinhai.de
jingpin.org
||jingpin.org
jinpianwang.com
.jinroukong.com
ac.jiruan.net
||jitouch.com
.jizzthis.com
jjgirls.com
.jkb.cc
|http://jkb.cc
jkforum.net
||jma.go.jp
research.jmsc.hku.hk/social
weiboscope.jmsc.hku.hk
.jmscult.com
|http://jmscult.com
||joachims.org
||jobso.tv
.sunwinism.joinbbs.net
||joinclubhouse.com
||jornaldacidadeonline.com.br
.journalchretien.net
||journalofdemocracy.org
.joymiihub.com
.joyourself.com
jpopforum.net
||jsdelivr.net
||fiddle.jshell.net
.jubushoushen.com
||jubushoushen.com
!--Doamin parking
.juhuaren.com
||juliereyc.com
||junauza.com
.june4commemoration.org
.junefourth-20.net
||junefourth-20.net
||bbs.junglobal.net
.juoaa.com
|http://juoaa.com
justfreevpn.com
||justhost.ru
.justicefortenzin.org
justpaste.it
||justmysocks1.net
justtristan.com
juyuange.org
juziyue.com
||juziyue.com
||jwmusic.org
@@||music.jwmusic.org
||cdn.jwplayer.com
.jyxf.net
!--------------------KK-------------------------
||k-doujin.net
||ka-wai.com
||kadokawa.co.jp
.kagyu.org
||kagyu.org.za
.kagyumonlam.org
.kagyunews.com.hk
.kagyuoffice.org
||kagyuoffice.org
||kagyuoffice.org.tw
.kaiyuan.de
.kakao.com
||kakao.com
.kalachakralugano.org
.kankan.today
.kannewyork.com
||kannewyork.com
.kanshifang.com
||kanshifang.com
||kantie.org
kanzhongguo.com
kanzhongguo.eu
.kaotic.com
||kaotic.com
||karayou.com
karkhung.com
.karmapa.org
.karmapa-teachings.org
||kawase.com
.kba-tx.org
.kcoolonline.com
.kebrum.com
||kebrum.com
.kechara.com
.keepandshare.com/visit/visit_page.php?i=688154
!--||keepvid.com
.keezmovies.com
.kendincos.net
.kenengba.com
||kenengba.com
||keontech.net
.kepard.com
||kepard.com
wiki.keso.cn/Home
||keycdn.com
.khabdha.org
.khmusic.com.tw
||kichiku-doujinko.com
.kik.com
||kik.com
bbs.kimy.com.tw
.kindleren.com
|http://kindleren.com
|http://www.kindleren.com
.kingdomsalvation.org
||kingdomsalvation.org
kinghost.com
!--.kingstone.com.tw/book/
||kingstone.com.tw
.kink.com
.kinokuniya.com
||kinokuniya.com
killwall.com
||killwall.com
||kinmen.travel
.kir.jp
.kissbbao.cn
|http://kiwi.kz
||kk-whys.co.jp
!--||kmt.org.tw
.kmuh.org.tw
.knowledgerush.com/kr/encyclopedia
||knowyourmeme.com
.kobo.com
||kobo.com
.kobobooks.com
||kobobooks.com
||kodingen.com
@@||www.kodingen.com
||kompozer.net
.konachan.com
||konachan.com
.kone.com
||koolsolutions.com
.koornk.com
||koornk.com
||koranmandarin.com
.korenan2.com
||kqes.net
|http://gojet.krtco.com.tw
.ksdl.org
.ksnews.com.tw
||ktzhk.com
.kui.name/event
||kukuku.uk
kun.im
.kurashsultan.com
||kurtmunger.com
kusocity.com
||kwcg.ca
||kwok7.com
.kwongwah.com.my
||kwongwah.com.my
.kxsw.life
||kxsw.life
.kyofun.com
kyohk.net
||kyoyue.com
.kyzyhello.com
||kyzyhello.com
.kzeng.info
||kzeng.info
!--------------------LL-------------------------
la-forum.org
ladbrokes.com
||labiennale.org
.lagranepoca.com
||lagranepoca.com
||lala.im
.lalulalu.com
.lama.com.tw
||lama.com.tw
.lamayeshe.com
|http://lamayeshe.com
|http://www.lamenhu.com
.lamnia.co.uk
||lamnia.co.uk
lamrim.com
||landofhope.tv
.lanterncn.cn
|http://lanterncn.cn
.lantosfoundation.org
.laod.cn
|http://laod.cn
laogai.org
||laogai.org
||laogairesearch.org
laomiu.com
.laoyang.info
|http://laoyang.info
||laptoplockdown.com
.laqingdan.net
||laqingdan.net
||larsgeorge.com
.lastcombat.com
|http://lastcombat.com
||lastfm.es
latelinenews.com
||lausan.hk
||le-vpn.com
.leafyvpn.net
||leafyvpn.net
||ledger.com
leeao.com.cn/bbs/forum.php
!--||leecheukyan.org
lefora.com
||left21.hk
.legalporno.com
.legsjapan.com
|http://leirentv.ca
leisurecafe.ca
||lematin.ch
.lemonde.fr
||lenwhite.com
||leorockwell.com
lerosua.org
||lerosua.org
blog.lester850.info
||lesoir.be
.letou.com
letscorp.net
||letscorp.net
||ocsp.int-x3.letsencrypt.org
||ss.levyhsu.com
!69.16.175.42
||cdn.assets.lfpcontent.com
.lhakar.org
|http://lhakar.org
.lhasocialwork.org
.liangyou.net
||liangyou.net
.lianyue.net
||liaowangxizang.net
.liaowangxizang.net
||liberal.org.hk
||libertysculpturepark.com
||libertytimes.com.tw
blogs.libraryinformationtechnology.com/jxyz
||libredd.it
||lighten.org.tw
||lightnovel.cn
limiao.net
linkuswell.com
abitno.linpie.com/use-ipv6-to-fuck-gfw
||line.me
||line-apps.com
.linglingfa.com
||lingvodics.com
.link-o-rama.com
|http://link-o-rama.com
||linkedin.com
.linkideo.com
||api.linksalpha.com
||apidocs.linksalpha.com
||www.linksalpha.com
||help.linksalpha.com
||linux.org.hk
linuxtoy.org/archives/installing-west-chamber-on-ubuntu
.lionsroar.com
.lipuman.com
||liquidvpn.com
||greatfire.us7.list-manage.com
||listennotes.com
||listentoyoutube.com
listorious.com
.liu-xiaobo.org
||liudejun.com
.liuhanyu.com
.liujianshu.com
||liujianshu.com
.liuxiaobo.net
||liuxiaobo.net
liuxiaotong.com
||liuxiaotong.com
.livedoor.jp
.liveleak.com
||liveleak.com
||livemint.com
livestream.com
||livestream.com
||livingonline.us
||livingstream.com
||livevideo.com
.livevideo.com
.liwangyang.com
lizhizhuangbi.com
lkcn.net
||chat.lmsys.org
||lncn.org
.load.to
.lobsangwangyal.com
.localdomain.ws
||localdomain.ws
localpresshk.com
||lockestek.com
logbot.net
||logiqx.com
secure.logmein.com
||secure.logmein.com
||logos.com.hk
.londonchinese.ca
.longhair.hk
longmusic.com
||longtermly.net
||lookpic.com
.looktoronto.com
|http://looktoronto.com
.lotsawahouse.org/tibetan-masters/fourteenth-dalai-lama
.lotuslight.org.hk
.lotuslight.org.tw
hkreporter.loved.hk
!--403?
||lpsg.com
||lrfz.com
.lrip.org
||lrip.org
.lsd.org.hk
||lsd.org.hk
lsforum.net
.lsm.org
||lsm.org
.lsmchinese.org
||lsmchinese.org
.lsmkorean.org
||lsmkorean.org
.lsmradio.com/rad_archives
.lsmwebcast.com
.ltn.com.tw
||ltn.com.tw
||luckydesigner.space
.luke54.com
.luke54.org
.lupm.org
||lupm.org
||lushstories.com
luxebc.com
lvhai.org
||lvhai.org
||lvv2.com
.lyfhk.net
|http://lyfhk.net
||lzjscript.com
.lzmtnews.org
||lzmtnews.org
!--------------------MM-------------------------
http://*.m-team.cc
!--m-team.cc/forum
.macrovpn.com
macts.com.tw
||mad-ar.ch
||madrau.com
||madthumbs.com
||magic-net.info
mahabodhi.org
my.mail.ru
.maiplus.com
|http://maiplus.com
.maizhong.org
makkahnewspaper.com
.mamingzhe.com
manicur4ik.ru
||manyvoices.news
.maplew.com
|http://maplew.com
||marc.info
marguerite.su
||martincartoons.com
maskedip.com
.maiio.net
.mail-archive.com
.malaysiakini.com
||makemymood.com
.manchukuo.net
.maniash.com
|http://maniash.com
.mansion.com
.mansionpoker.com
!--||marines.mil
!--markmail.org*message
||martau.com
|http://blog.martinoei.com
.martsangkagyuofficial.org
|http://martsangkagyuofficial.org
maruta.be/forget
.marxist.com
||marxist.net
.marxists.org/chinese
!--||mashable.com
||matainja.com
||mathable.io
||mathiew-badimon.com
||matrix.org
||matsushimakaede.com
||matters.town
||maturejp.com
mayimayi.com
.maxing.jp
.mcaf.ee
|http://mcaf.ee
||mcadforums.com
mcfog.com
mcreasite.com
.md-t.org
||md-t.org
||meansys.com
.media.org.hk
.mediachinese.com
||mediachinese.com
.mediafire.com/?
.mediafire.com/download
.mediafreakcity.com
||mediafreakcity.com
.medium.com
||medium.com
.meetav.com
||meetup.com
mefeedia.com
jihadintel.meforum.org
||mega.co.nz
||mega.io
||mega.nz
||megaproxy.com
||megarotic.com
megavideo.com
||megurineluka.com
||meizhong.blog
||meizhong.report
.meltoday.com
.memehk.com
||memehk.com
memorybbs.com
.memri.org
.memrijttm.org
||mercdn.net
.mercyprophet.org
||mercyprophet.org
||mergersandinquisitions.org
.meridian-trust.org
||meridian-trust.org
.meripet.biz
||meripet.biz
.meripet.com
||meripet.com
||merit-times.com.tw
meshrep.com
.mesotw.com/bbs
metacafe.com/watch
||metafilter.com
||meteorshowersonline.com
||metro.taipei
.metrohk.com.hk/?cmd=detail&categoryID=2
||metrolife.ca
.metroradio.com.hk
|http://metroradio.com.hk
||mewe.com
meyou.jp
.meyul.com
||mgoon.com
||mgstage.com
||mh4u.org
mhradio.org
|http://michaelanti.com
||michaelmarketl.com
|http://bbs.mikocon.com
.microvpn.com
|http://microvpn.com
middle-way.net
.mihk.hk/forum
.mihr.com
mihua.org
!--IP
||mikesoltys.com
.milph.net
|http://milph.net
.milsurps.com
mimiai.net
.mimivip.com
.mimivv.com
.mindrolling.org
|http://mindrolling.org
||mingdemedia.org
.minghui.or.kr
|http://minghui.or.kr
minghui.org
||minghui.org
minghui-a.org
minghui-b.org
minghui-school.org
.mingjinglishi.com
||mingjinglishi.com
mingjingnews.com
||mingjingtimes.com
.mingpao.com
||mingpao.com
.mingpaocanada.com
.mingpaomonthly.com
|http://mingpaomonthly.com
mingpaonews.com
.mingpaony.com
.mingpaosf.com
.mingpaotor.com
.mingpaovan.com
.mingshengbao.com
.minhhue.net
.miniforum.org
.ministrybooks.org
.minzhuhua.net
||minzhuhua.net
minzhuzhanxian.com
minzhuzhongguo.org
||miroguide.com
mirrorbooks.com
||mirrormedia.mg
.mist.vip
||thecenter.mit.edu
||scratch.mit.edu
.mitao.com.tw
.mitbbs.com
||mitbbs.com
mitbbsau.com
.mixero.com
||mixero.com
||mixi.jp
mixpod.com
.mixx.com
||mixx.com
||mizzmona.com
.mk5000.com
.mlcool.com
||mlzs.work
.mm-cg.com
||mmaaxx.com
.mmmca.com
mnewstv.com
||mobatek.net
.mobile01.com
||mobile01.com
||mobileways.de
.mobypicture.com
|http://moby.to
||mod.io
||modernchinastudies.org
||moeerolibrary.com
wiki.moegirl.org
.mofaxiehui.com
.mofos.com
||mog.com
||mohu.rocks
molihua.org
||momoshop.com.tw
||mondex.org
||money-link.com.tw
|http://www.monlamit.org
||moon.fm
.moonbbs.com
||moonbbs.com
||moptt.tw
||monica.im
||monitorchina.org
||monocloud.me
bbs.morbell.com
||morningsun.org
||moroneta.com
.motherless.com
|http://motherless.com
motor4ik.ru
.mousebreaker.com
!--||movabletype.com
.movements.org
||movements.org
||moviefap.com
||www.moztw.org
.mp3buscador.com
||mpettis.com
.mpfinance.com
||mpfinance.com
.mpinews.com
||mpinews.com
mponline.hk
.mqxd.org
|http://mqxd.org
mrtweet.com
||mrtweet.com
news.hk.msn.com
news.msn.com.tw
msguancha.com
.mswe1.org
|http://mswe1.org
||mthruf.com
||mubi.com
muchosucko.com
||multiply.com
multiproxy.org
multiupload.com
.mullvad.net
||mullvad.net
.mummysgold.com
.murmur.tw
|http://murmur.tw
.musicade.net
.muslimvideo.com
||muzi.com
||muzi.net
||mx981.com
.my-formosa.com
.my-proxy.com
.my-private-network.co.uk
||my-private-network.co.uk
forum.my903.com
.myactimes.com/actimes
||myanniu.com
.myaudiocast.com
||myaudiocast.com
.myav.com.tw/bbs
.mybbs.us
.myca168.com
.mycanadanow.com
||bbs.mychat.to
||mychinamyhome.com
.mychinamyhome.com
.mychinanet.com
.mychinanews.com
||mychinanews.com
.mychinese.news
||mycnnews.com
||mykomica.org
mycould.com/discuz
.myeasytv.com
||myeclipseide.com
.myforum.com.hk
||myforum.com.hk
||myforum.com.uk
.myfreecams.com
.myfreepaysite.com
.myfreshnet.com
.myiphide.com
||myiphide.com
forum.mymaji.com
mymediarom.com/files/box
||mymoe.moe
||mymusic.net.tw
||myparagliding.com
||mypopescu.com
myradio.hk/podcast
.myreadingmanga.info
mysinablog.com
.myspace.com
!--.blogs.myspace.com
!--||blogs.myspace.com
!--vids.myspace.com/index.cfm?fuseaction=vids.
!--viewmorepics.myspace.com
||myspacecdn.com
.mytalkbox.com
.mytizi.com
!--------------------NN-------------------------
||naacoalition.org
old.nabble.com
||naitik.net
.nakido.com
||nakido.com
.nakuz.com/bbs
||nalandabodhi.org
||nalandawest.org
.namgyal.org
namgyalmonastery.org
||namsisi.com
.nanyang.com
||nanyang.com
.nanyangpost.com
||nanyangpost.com
.nanzao.com
!--.nanzao.com/sc/china/20223
!--.nanzao.com/sc/hk-macau-tw
.naol.ca
.naol.cc
uighur.narod.ru
.nat.moe
||nat.moe
cyberghost.natado.com
||national-lottery.co.uk
||nationalawakening.org
||nationalinterest.org
news.nationalgeographic.com/news/2014/06/140603-tiananmen-square
||nationalreview.com
.nationsonline.org/oneworld/tibet
||line.naver.jp
||navyfamily.navy.mil
||navyreserve.navy.mil
||nko.navy.mil
||usno.navy.mil
naweeklytimes.com
||nbcnews.com
.nbtvpn.com
|http://nbtvpn.com
nccwatch.org.tw
.nch.com.tw
.ncn.org
||nchrd.org
||ncn.org
||etools.ncol.com
.nde.de
||ndi.org
.ndr.de
.ned.org
||nekoslovakia.net
||neowin.net
||nepusoku.com
||net-fits.pro
||netalert.me
!--bbsnew.netbig.com
bbs.netbig.com
.netbirds.com
netcolony.com
bolin.netfirms.com
||netflav.com
||netme.cc
||netsarang.com
netsneak.com
.network54.com
networkedblogs.com
.networktunnel.net
neverforget8964.org
new-3lunch.net
.new-akiba.com
.new96.ca
.newcenturymc.com
|http://newcenturymc.com
newcenturynews.com
||newchen.com
.newchen.com
.newgrounds.com
||newhighlandvision.com
newipnow.com
.newlandmagazine.com.au
||newmitbbs.com
.newnews.ca
news100.com.tw
newschinacomment.org
.newscn.org
||newscn.org
newspeak.cc/story
.newsancai.com
||newsancai.com
.newsdetox.ca
.newsdh.com
||newsmax.com
||newstamago.com
||newstapa.org
||newstatesman.com
newstarnet.com
||newsweek.com
.newtaiwan.com.tw
newtalk.tw
||newtalk.tw
||newyorker.com
newyorktimes.com
||nexon.com
.next11.co.jp
||nextdigital.com.hk
.nextmag.com.tw
!--hk*.nextmedia.com
!--tw*.nextmedia.com
!--static*.nextmedia.com
.nextmedia.com
||nexton-net.jp
||nexttv.com.tw
.nfjtyd.com
||co.ng.mil
||nga.mil
ngensis.com
||ngodupdongchung.com
.nhentai.net
|http://nhentai.net
.nhk-ondemand.jp
.nicovideo.jp/watch
||nicovideo.jp
||nighost.org
av.nightlife141.com
ninecommentaries.com
.ninjacloak.com
||ninjaproxy.ninja
nintendium.com
taiwanyes.ning.com
usmgtcg.ning.com/forum
||niusnews.com
||njactb.org
njuice.com
||njuice.com
||nlfreevpn.com
||nmsl.website
||nnews.eu
!--no-ip.com#NOIP
.ddns.net/
.gooddns.info
||gotdns.ch
.maildns.xyz
.no-ip.org
.opendn.xyz
.servehttp.com
sytes.net
.whodns.xyz
.zapto.org
|http://dynupdate.no-ip.com/
||nobel.se
!--.nobelprize.org
!--|http://nobelprize.org
nobelprize.org/nobel_prizes/peace/laureates/1989
nobelprize.org/nobel_prizes/peace/laureates/2010
nobodycanstop.us
||nobodycanstop.us
||nokogiri.org
||nokola.com
noodlevpn.com
.norbulingka.org
nordvpn.com
||nordvpn.com
||notepad-plus-plus.org
||novelasia.com
.news.now.com
|http://news.now.com
!--|http://news.now.com/home*
news.now.com%2Fhome
||nownews.com
.nowtorrents.com
.noypf.com
||noypf.com
||npa.go.jp
.npnt.me
|http://npnt.me
.nps.gov
.nradio.me
|http://nradio.me
.nrk.no
||nrk.no
.ntd.tv
||ntd.tv
.ntdtv.com
||ntdtv.com
||ntdtv.com.tw
.ntdtv.co.kr
ntdtv.ca
ntdtv.org
ntdtv.ru
ntdtvla.com
.ntrfun.com
||cbs.ntu.edu.tw
||media.nu.nl
.nubiles.net
||nuexpo.com
.nukistream.com
||nurgo-software.com
||nutaku.net
||nutsvpn.work
.nuvid.com
||nvdst.com
nuzcom.com
.nvquan.org
.nvtongzhisheng.org
|http://nvtongzhisheng.org
.nwtca.org
|http://nyaa.eu
||nyaa.si
||nybooks.com
.nydus.ca
nylon-angel.com
nylonstockingsonline.com
||nypost.com
!--nysingtao.com
.nzchinese.com
||nzchinese.net.nz
!--------------------OO-------------------------
||oann.com
observechina.net
.obutu.com
ocaspro.com
occupytiananmen.com
oclp.hk
.ocreampies.com
||october-review.org
||odysee.com
offbeatchina.com
||officeoftibet.com
|http://ofile.org
||ogaoga.org
twtr2src.ogaoga.org
.ogate.org
||ogate.org
www2.ohchr.org/english/bodies/cat/docs/ngos/II_China_41.pdf
||ohmyrss.com
.oikos.com.tw/v4
.oiktv.com
oizoblog.com
.ok.ru
||ok.ru
.okayfreedom.com
||okayfreedom.com
||okk.tw
|http://filmy.olabloga.pl/player
old-cat.net
||olevod.com
||olumpo.com
.olympicwatch.org
||omct.org
omgili.com
||omnitalk.com
||omnitalk.org
||omny.fm
cling.omy.sg
forum.omy.sg
news.omy.sg
showbiz.omy.sg
||on.cc
||onedrive.live.com
||onion.city
||onion.ly
.onlinecha.com
||onlineyoutube.com
||onlygayvideo.com
.onlytweets.com
|http://onlytweets.com
onmoon.net
onmoon.com
.onthehunt.com
|http://onthehunt.com
.oopsforum.com
open.com.hk
openallweb.com
opendemocracy.net
||opendemocracy.net
.openervpn.in
openid.net
||openid.net
.openleaks.org
||openleaks.org
||openstreetmap.org
||opentech.fund
openvpn.net
||openvpn.net
||openwebster.com
.openwrt.org.cn
@@||openwrt.org.cn
my.opera.com/dahema
||demo.opera-mini.net
.opus-gaming.com
|http://opus-gaming.com
www.orchidbbs.com
.organcare.org.tw
organharvestinvestigation.net
.orgasm.com
.orgfree.com
||oricon.co.jp
||orient-doll.com
orientaldaily.com.my
||orientaldaily.com.my
!--orientaldaily.on.cc
||orn.jp
t.orzdream.com
||t.orzdream.com
tui.orzdream.com
||orzistic.org
||osfoora.com
.otnd.org
||otnd.org
||otto.de
||ourdearamy.com
oursogo.com
.oursteps.com.au
||oursteps.com.au
.oursweb.net
||ourtv.hk
xinqimeng.over-blog.com
||overcast.fm
||overdaily.org
||overplay.net
share.ovi.com/media
||ovpn.com
|http://owl.li
|http://ht.ly
|http://htl.li
|http://mash.to
www.owind.com
||owltail.com
||oxfordscholarship.com
|http://www.oxid.it
oyax.com
oyghan.com/wps
.ozchinese.com/bbs
||ow.ly
bbs.ozchinese.com
.ozvoice.org
||ozvoice.org
.ozxw.com
.ozyoyo.com
!--------------------PP-------------------------
||pachosting.com
.pacificpoker.com
.packetix.net
||pacopacomama.com
.padmanet.com
||page.link
page2rss.com
||pagodabox.com
.palacemoon.com
forum.palmislife.com
||eriversoft.com
.paldengyal.com
paljorpublications.com
.paltalk.com
!--||pangci.net
||pandapow.co
.pandapow.net
.pandavpn-jp.com
||pandavpn-jp.com
||pandavpnpro.com
.panluan.net
||panluan.net
||pao-pao.net
paper.li
paperb.us
.paradisehill.cc
.paradisepoker.com
||parler.com
||parsevideo.com
.partycasino.com
.partypoker.com
.passion.com
||passion.com
.passiontimes.hk
pastebin.com
.pastie.org
||pastie.org
||blog.pathtosharepoint.com
||patreon.com
||pawoo.net
pbs.org/wgbh/pages/frontline/tankman
pbs.org/wgbh/pages/frontline/tibet
video.pbs.org
!--Pbwiki
pbwiki.com
||pbworks.com
||developers.box.net
||wiki.oauth.net
||wiki.phonegap.com
||wiki.jqueryui.com
||pbxes.com
||pbxes.org
pcdvd.com.tw
||pcgamestorrents.com
.pchome.com.tw
||pcij.org
.pcstore.com.tw
||pct.org.tw
pdetails.com
||pdproxy.com
||peace.ca
peacefire.org
peacehall.com
||peacehall.com
|http://pearlher.org
.peeasian.com
||peing.net
.pekingduck.org
||pekingduck.org
.pemulihan.or.id
|http://pemulihan.or.id
||pen.io
penchinese.com
||penchinese.net
.penchinese.net
||blog.pentalogic.net
.penthouse.com
||pentoy.hk
.peoplebookcafe.com
.peoplenews.tw
||peoplenews.tw
.peopo.org
||peopo.org
.percy.in
.perfectgirls.net
||perfect-privacy.com
||perplexity.ai
.persecutionblog.com
.persiankitty.com
phapluan.org
.phayul.com
||phayul.com
philborges.com
||phncdn.com
||photodharma.net
||photofocus.com
||phuquocservices.com
||picacomiccn.com
.picidae.net
||img*.picturedip.com
picturesocial.com
||pin-cong.com
.pin6.com
||pin6.com
.ping.fm
||ping.fm
||pinimg.com
.pinkrod.com
||pinoy-n.com
||pinterest.at
||pinterest.ca
||pinterest.co.kr
||pinterest.co.uk
.pinterest.com
||pinterest.com
||pinterest.com.mx
||pinterest.de
||pinterest.dk
||pinterest.fr
||pinterest.jp
||pinterest.nl
||pinterest.se
.pipii.tv
.piposay.com
piraattilahti.org
.piring.com
||pixeldrain.com
||pixelqi.com
||css.pixnet.in
||pixnet.net
.pixnet.net
.pk.com
||placemix.com
!--.planetsuzy.org
|http://pictures.playboy.com
||playboy.com
.playboyplus.com
||playboyplus.com
||player.fm
.playno1.com
||playno1.com
||playpcesor.com
plays.com.tw
||plexvpn.pro
||m.plixi.com
plm.org.hk
plunder.com
.plurk.com
||plurk.com
.plus28.com
.plusbb.com
.pmatehunter.com
||pmatehunter.com
.pmates.com
||po2b.com
pobieramy.top
!--||pocoo.org
||podbean.com
||podictionary.com
||poe.com
.pokerstars.com
||pokerstars.com
||pokerstars.net
||zh.pokerstrategy.com
||politicalchina.org
||politicalconsultation.org
.politiscales.net
||poloniex.com
||polymerhk.com
.popo.tw
!--||popularpages.net
||popvote.hk
||popxi.click
.popyard.com
||popyard.org
.porn.com
.porn2.com
.porn5.com
.pornbase.org
.pornerbros.com
||pornhd.com
.pornhost.com
.pornhub.com
||pornhub.com
.pornhubdeutsch.net
|http://pornhubdeutsch.net
||pornmm.net
.pornoxo.com
.pornrapidshare.com
||pornrapidshare.com
.pornsharing.com
|http://pornsharing.com
.pornsocket.com
.pornstarclub.com
||pornstarclub.com
.porntube.com
.porntubenews.com
.porntvblog.com
||porntvblog.com
.pornvisit.com
.portablevpn.nl
||poskotanews.com
.post01.com
.post76.com
||post76.com
.post852.com
||post852.com
postadult.com
.postimg.org
||potvpn.com
||pourquoi.tw
||powercx.com
.powerphoto.org
||www.powerpointninja.com
||presidentlee.tw
||cdn.printfriendly.com
.pritunl.com
provpnaccounts.com
||provpnaccounts.com
.proxfree.com
||proxfree.com
proxyanonimo.es
.proxynetwork.org.uk
||proxynetwork.org.uk
||pts.org.tw
.pttvan.org
pubu.com.tw
puffinbrowser.com
pureinsight.org
.pushchinawall.com
.putty.org
||putty.org
!-------------Posterous-----
||calebelston.com
||blog.fizzik.com
||nf.id.au
||sogrady.me
||vatn.org
||ventureswell.com
||whereiswerner.com
.power.com
||power.com
powerapple.com
||powerapple.com
||abc.pp.ru
heix.pp.ru
||prayforchina.net
||premeforwindows7.com
||presentationzen.com
||prestige-av.com
.prisoneralert.com
||pritunl.com
||privacybox.de
.private.com/home
||privateinternetaccess.com
privatepaste.com
||privatepaste.com
privatetunnel.com
||privatetunnel.com
||privatevpn.com
||privoxy.org
||procopytips.com
||project-syndicate.org
||proton.me
provideocoalition.com
||prosiben.de
proxifier.com
||proxomitron.info
.proxpn.com
||proxpn.com
.proxylist.org.uk
||proxylist.org.uk
.proxypy.net
||proxypy.net
proxyroad.com
.proxytunnel.net
!--403 maybe
||proyectoclubes.com
prozz.net
psblog.name
||psblog.name
||pshvpn.com
||psiphon.ca
.psiphon3.com
||psiphon3.com
.psiphontoday.com
||pstatic.net
||pt.im
.ptt.cc
||ptt.cc
||pttgame.com
.puffstore.com
.puuko.com
||pullfolio.com
.punyu.com/puny
||pureconcepts.net
||pureinsight.org
||purepdf.com
||purevpn.com
.purplelotus.org
.pursuestar.com
||pursuestar.com
||nitter.pussthecat.org
.pussyspace.com
.putihome.org
.putlocker.com/file
pwned.com
||pximg.net
python.com
.python.com.tw
||python.com.tw
pythonhackers.com/p
ss.pythonic.life
!--------------------QQ-------------------------
.qanote.com
||qanote.com
||qbittorrent.org
||qgirl.com.tw
||qianbai.tw
||qiandao.today
||qiangwaikan.com
.qi-gong.me
||qi-gong.me
!--#921
||qiangyou.org
.qidian.ca
.qienkuen.org
||qienkuen.org
||qiwen.lu
qixianglu.cn
bbs.qmzdd.com
.qkshare.com
qoos.com
||qoos.com
||efksoft.com
||qstatus.com
||qtweeter.com
||qtrac.eu
.quannengshen.org
||quannengshen.org
quantumbooter.net
||quitccp.net
.quitccp.net
||quitccp.org
.quitccp.org
.quora.com/Chinas-Future
.quran.com
|http://quran.com
.quranexplorer.com
qusi8.net
.qvodzy.org
nemesis2.qx.net/pages/MyEnTunnel
qxbbs.org
!--------------------RR-------------------------
||r0.ru
||radio-canada.ca
||radio-en-ligne.fr
||rael.org
radicalparty.org
||radio.garden
||radioaustralia.net.au
.radiohilight.net
||radiohilight.net
||radioline.co
opml.radiotime.com
||radiovaticana.org
||radiovncr.com
||raggedbanner.com
||raidcall.com.tw
.raidtalk.com.tw
.rainbowplan.org/bbs
|https://raindrop.io/
.raizoji.or.jp
|http://raizoji.or.jp
rangwang.biz
rangzen.net
rangzen.org
|http://blog.ranxiang.com/
ranyunfei.com
||ranyunfei.com
.rapbull.net
!--|http://rapidgator.net/
||rapidmoviez.com
rapidvpn.com
||rapidvpn.com
||rarbgprx.org
.raremovie.cc
|http://raremovie.cc
.raremovie.net
|http://raremovie.net
||rationalwiki.org
||rawgit.com
||rawgithub.com
!--.rayfme.com/bbs
||razyboard.com
rcinet.ca
.read100.com
.readingtimes.com.tw
||readingtimes.com.tw
||readmoo.com
.readydown.com
|http://readydown.com
.realcourage.org
.realitykings.com
||realitykings.com
.realraptalk.com
.realsexpass.com
||reason.com
.recordhistory.org
.recovery.org.tw
|http://online.recoveryversion.org
||recoveryversion.com.tw
||red-lang.org
redballoonsolidarity.org
||redbubble.com
.redchinacn.net
|http://redchinacn.net
redchinacn.org
redtube.com
referer.us
||referer.us
||reflectivecode.com
relaxbbs.com
.relay.com.tw
.releaseinternational.org
||religionnews.com
religioustolerance.org
renminbao.com
||renminbao.com
.renyurenquan.org
||renyurenquan.org
|http://certificate.revocationcheck.com
subacme.rerouted.org
||resilio.com
.reuters.com
||reuters.com
||reutersmedia.net
.revleft.com
||resistchina.org
retweetist.com
||retweetrank.com
!--connectedchina.reuters.com
!--|http://www.reuters.com/news/video
revver.com
.rfa.org
||rfa.org
.rfachina.com
.rfamobile.org
rfaweb.org
||rferl.org
.rfi.fr
||rfi.fr
||rfi.my
!--.rhcloud.com
!--Edgecast
|http://vds.rightster.com/
.rigpa.org
.rileyguide.com
||riku.me
.ritouki.jp
||ritter.vg
.rlwlw.com
||rlwlw.com
||rmbl.ws
.rmjdw.com
.rmjdw132.info
.roadshow.hk
.roboforex.com
||robustnessiskey.com
!--||roc-taiwan.org
||rocket-inc.net
|http://www2.rocketbbs.com/11/bbs.cgi?id=5mus
|http://www2.rocketbbs.com/11/bbs.cgi?id=freemgl
!--||rocmp.org
||rojo.com
||ronjoneswriter.com
||rolfoundation.org
||rolia.net
||rolsociety.org
.roodo.com
.rosechina.net
.rotten.com
||rou.video
.rsf.org
||rsf.org
.rsf-chinese.org
||rsf-chinese.org
.rsgamen.org
||rsshub.app
||phosphation13.rssing.com
.rssmeme.com
||rssmeme.com
||rtalabel.org
.rthk.hk
||rthk.hk
.rthk.org.hk
||rthk.org.hk
.rti.org.tw
||rti.org.tw
||rti.tw
.rtycminnesota.org
.ruanyifeng.com/blog*some_ways_to_break_the_great_firewall
rukor.org
||rule34.xxx
||rumble.com
.runbtx.com
.rushbee.com
||rusvpn.com
.ruten.com.tw
||ruten.com.tw
||rutracker.net
rutube.ru
.ruyiseek.com
.rxhj.net
|http://rxhj.net
!--------------------SS-------------------------
.s1s1s1.com
||s-cute.com
.s-dragon.org
||s1heng.com
|http://www.s4miniarchive.com
||s8forum.com
cdn1.lp.saboom.com
||sacks.com
sacom.hk
||sacom.hk
||sadpanda.us
||safechat.com
||safeguarddefenders.com
.safervpn.com
||safervpn.com
.saintyculture.com
|http://saintyculture.com
.saiq.me
||saiq.me
||sakuralive.com
.sakya.org
.salvation.org.hk
||salvation.org.hk
.samair.ru/proxy/type-01
.sambhota.org
||cn.sandscotaicentral.com
||sankakucomplex.com
||sankei.com
.sanmin.com.tw
sapikachu.net
savemedia.com
||savethesounds.info
.savetibet.de
||savetibet.de
savetibet.fr
savetibet.nl
.savetibet.org
||savetibet.org
savetibet.ru
.savetibetstore.org
||savetibetstore.org
||saveuighur.org
savevid.com
||say2.info
.sbme.me
|http://sbme.me
.sbs.com.au/yourlanguage
.scasino.com
|http://www.sciencemag.org/content/344/6187/953
.sciencenets.com
.scmp.com
||scmp.com
.scmpchinese.com
||scramble.io
.scribd.com
||scribd.com
||scriptspot.com
||search.com
.searchtruth.com
||searx.me
||seattlefdc.com
.secretchina.com
||secretchina.com
||secretgarden.no
.secretsline.biz
||secretsline.biz
||secureservercdn.net
||securetunnel.com
securityinabox.org
|https://securityinabox.org
.securitykiss.com
||securitykiss.com
||seed4.me
news.seehua.com
seesmic.com
||seevpn.com
||seezone.net
sejie.com
.sendspace.com
||sensortower.com
|http://tweets.seraph.me/
sesawe.net
||sesawe.net
.sesawe.org
||sethwklein.net
.setn.com
.settv.com.tw
forum.setty.com.tw
.sevenload.com
||sevenload.com
.sex.com
||sex.com
.sex-11.com
||sex3.com
||sex8.cc
.sexandsubmission.com
.sexbot.com
.sexhu.com
.sexhuang.com
sexinsex.net
||sexinsex.net
.sextvx.com
!--IP of SexInSex
67.220.91.15
67.220.91.18
67.220.91.23
|http://*.sf.net
.sfileydy.com
||sfshibao.com
.sftindia.org
.sftuk.org
||sftuk.org
||shadeyouvpn.com
shadow.ma
.shadowsky.xyz
.shadowsocks.asia
||www.shadowsocks.com
.shadowsocks.com
||shadowsocks.com.hk
.shadowsocks.org
||shadowsocks.org
||shadowsocks-r.com
|http://cn.shafaqna.com
||shahit.biz
.shambalapost.com
.shambhalasun.com
.shangfang.org
||shangfang.org
shapeservices.com
.sharebee.com
||sharecool.org
!--||sharkdolphin.com
sharpdaily.com.hk
||sharpdaily.com.hk
.sharpdaily.hk
.sharpdaily.tw
.shat-tibet.com
sheikyermami.com
.shellfire.de
||shellfire.de
.shenshou.org
shenyun.com
shenyunperformingarts.org
||shenyunperformingarts.org
||shenyunshop.com
shenzhoufilm.com
||shenzhoufilm.com
||shenzhouzhengdao.org
||sherabgyaltsen.com
.shiatv.net
.shicheng.org
shinychan.com
shipcamouflage.com
.shireyishunjian.com
.shitaotv.org
||shixiao.org
||shizhao.org
shizhao.org
shkspr.mobi/dabr
||shodanhq.com
||shooshtime.com
.shop2000.com.tw
||shopee.tw
.shopping.com
.showhaotu.com
.showtime.jp
||showwe.tw
.shutterstock.com
||shutterstock.com
ch.shvoong.com
.shwchurch.org
||shwchurch.org
.shwchurch3.com
|http://shwchurch3.com
.siddharthasintent.org
||sidelinesnews.com
.sidelinessportseatery.com
||signal.org
.sijihuisuo.club
.sijihuisuo.com
.silkbook.com
||simbolostwitter.com
simplecd.org
||simplecd.org
@@||simplecd.me
simpleproductivityblog.com
bbs.sina.com/
bbs.sina.com%2F
blog.sina.com.tw
dailynews.sina.com/
dailynews.sina.com%2F
forum.sina.com.hk
home.sina.com
||magazines.sina.com.tw
news.sina.com.hk
news.sina.com.tw
news.sinchew.com.my
.sinchew.com.my/node/
.sinchew.com.my/taxonomy/term
.singaporepools.com.sg
||singaporepools.com.sg
.singfortibet.com
.singpao.com.hk
singtao.com
||singtao.com
news.singtao.ca
.singtaousa.com
||singtaousa.com
!--||cdp.sinica.edu.tw
sino-monthly.com
||sinoca.com
||sinocast.com
sinocism.com
sinomontreal.ca
.sinonet.ca
.sinopitt.info
.sinoants.com
||sinoants.com
||sinoinsider.com
.sinoquebec.com
.sierrafriendsoftibet.org
sis.xxx
||sis001.com
sis001.us
.site2unblock.com
||site90.net
.sitebro.tw
||sitekreator.com
||siteks.uk.to
||sitemaps.org
.sjrt.org
|http://sjrt.org
||sjum.cn
||sketchappsources.com
||skimtube.com
||lab.skk.moe
||skybet.com
|http://users.skynet.be/reves/tibethome.html
.skyking.com.tw
bbs.skykiwi.com
|http://www.skype.com/intl/
|http://www.skype.com/zh-Hant
||skyvegas.com
.xskywalker.com
||xskywalker.com
||skyxvpn.com
m.slandr.net
.slaytizle.com
.sleazydream.com
||sleazyfork.org
||slheng.com
||slideshare.net
forum.slime.com.tw
.slinkset.com
||slickvpn.com
.slutload.com
||smartdnsproxy.com
.smarthide.com
||app.smartmailcloud.com
smchbooks.com
.smh.com.au/world/death-of-chinese-playboy-leaves-fresh-scratches-in-party-paintwork-20120903-25a8v
smhric.org
.smith.edu/dalailama
.smyxy.org
!--TODO-no-homepage
||snapchat.com
.snaptu.com
||snaptu.com
||sndcdn.com
sneakme.net
snowlionpub.com
home.so-net.net.tw/yisa_tsai
||soc.mil
||socialblade.com
.socks-proxy.net
||socks-proxy.net
.sockscap64.com
||sockslist.net
.socrec.org
|http://socrec.org
.sod.co.jp
.softether.org
||softether.org
.softether-download.com
||softether-download.com
||cdn.softlayer.net
||sogclub.com
sohcradio.com
||sohcradio.com
.sokmil.com
||sorting-algorithms.com
.sostibet.org
.soumo.info
||soup.io
@@||static.soup.io
.sobees.com
||sobees.com
socialwhale.com
.softether.co.jp
||softwarebychuck.com
blog.sogoo.org
soh.tw
||soh.tw
sohfrance.org
||sohfrance.org
chinese.soifind.com
sokamonline.com
||solana.com
.solidaritetibet.org
.solidfiles.com
||somee.com
.songjianjun.com
||songjianjun.com
.sonicbbs.cc
.sonidodelaesperanza.org
.sopcast.com
.sopcast.org
||nakedsecurity.sophos.com
.sorazone.net
||sos.org
bbs.sou-tong.org
.soubory.com
|http://soubory.com
.soul-plus.net
.soulcaliburhentai.net
||soulcaliburhentai.net
||soundcloud.com
!--|https://soundcloud.com/punkgod
.soundofhope.kr
soundofhope.org
||soundofhope.org
||soupofmedia.com
!--.sourceforge.net
!-|http://sourceforge.net
|http://sourceforge.net/p*/shadowsocksgui/
.sourcewadio.com
||south-plus.org
southnews.com.tw
sowers.org.hk
||wlx.sowiki.net
||spankbang.com
.spankingtube.com
.spankwire.com
||spb.com
||speakerdeck.com
||speedify.com
spem.at
||spencertipping.com
||spendee.com
||spicevpn.com
.spideroak.com
||spideroak.com
.spike.com
.spotflux.com
||spotflux.com
||spreaker.com
.spring4u.info
||spring4u.info
||springwood.me
||sproutcore.com
||sproxy.info
||squirrelvpn.com
||srocket.us
.ss-link.com
||ss-link.com
.ssglobal.co/wp
|http://ssglobal.co
.ssglobal.me
||ssh91.com
.sspro.ml
|http://sspro.ml
.ssrshare.com
||ssrshare.com
||sss.camp
!--|http://cdn.sstatic.net/
||sstm.moe
||sstmlt.moe
sstmlt.net
||sstmlt.net
|http://stackoverflow.com/users/895245
.stage64.hk
||stage64.hk
||standupfortibet.org
||standwithhk.org
stanford.edu/group/falun
usinfo.state.gov
||statueofdemocracy.org
.starfishfx.com
.starp2p.com
||starp2p.com
.startpage.com
||startpage.com
.startuplivingchina.com
|http://startuplivingchina.com
||static-economist.com
||stboy.net
||stc.com.sa
||steel-storm.com
.steganos.com
||steganos.com
.steganos.net
.stepchina.com
!--||stepmania.com
ny.stgloballink.com
hd.stheadline.com/news/realtime
sthoo.com
||sthoo.com
.stickam.com
stickeraction.com/sesawe
.stileproject.com
.sto.cc
.stoporganharvesting.org
||storagenewsletter.com
.storm.mg
||storm.mg
.stoptibetcrisis.net
||stoptibetcrisis.net
||storify.com
||storj.io
.stormmediagroup.com
||stoweboyd.com
||straitstimes.com
stranabg.com
||straplessdildo.com
||streamable.com
||streamate.com
||streamingthe.net
streema.com/tv/NTDTV_Chinese
cn.streetvoice.com/article
cn.streetvoice.com/diary
cn2.streetvoice.com
tw.streetvoice.com
.strikingly.com
||strongvpn.com
.strongwindpress.com
.student.tw/db
||studentsforafreetibet.org
||stumbleupon.com
stupidvideos.com
||substack.com
.successfn.com
panamapapers.sueddeutsche.de
.sugarsync.com
||sugarsync.com
.sugobbs.com
||sugumiru18.com
||suissl.com
summify.com
.sumrando.com
||sumrando.com
sun1911.com
||sundayguardianlive.com
.sunporno.com
||sunmedia.ca
||sunporno.com
.sunskyforum.com
.sunta.com.tw
.sunvpn.net
.suoluo.org
.superfreevpn.com
.supervpn.net
||supervpn.net
.superzooi.com
|http://superzooi.com
.suppig.net
.suprememastertv.com
|http://suprememastertv.com
.surfeasy.com
||surfeasy.com
.surfeasy.com.au
|http://surfeasy.com.au
||surfshark.com
||surrenderat20.net
.svsfx.com
.swissinfo.ch
||swissinfo.ch
.swissvpn.net
||swissvpn.net
switchvpn.net
||switchvpn.net
.sydneytoday.com
||sydneytoday.com
.sylfoundation.org
||sylfoundation.org
||syncback.com
sysresccd.org
.sytes.net
blog.syx86.com/2009/09/puff
blog.syx86.cn/2009/09/puff
.szbbs.net
.szetowah.org.hk
!--------------------TT-------------------------
||t-g.com
.t35.com
.t66y.com
||t66y.com
||esg.t91y.com
.taa-usa.org
|http://taa-usa.org
.taaze.tw
||taaze.tw
|http://www.tablesgenerator.com/
tabtter.jp
.tacem.org
.taconet.com.tw
||taedp.org.tw
.tafm.org
.tagwa.org.au
tagwalk.com
||tagwalk.com
tahr.org.tw
.taipeisociety.org
||taipeisociety.org
||taipeitimes.com
||taisounds.com
.taiwanbible.com
.taiwancon.com
.taiwandaily.net
||taiwandaily.net
.taiwandc.org
!--||taiwanembassy.org
||taiwanhot.net
.taiwanjustice.com
taiwankiss.com
taiwannation.com
taiwannation.com.tw
||taiwanncf.org.tw
||taiwannews.com.tw
|http://www.taiwanonline.cc/
!--||taiwantoday.tw
taiwantp.net
||taiwantt.org.tw
taiwanus.net
taiwanyes.com
taiwan-sex.com
.talk853.com
.talkboxapp.com
||talkboxapp.com
.talkcc.com
||talkcc.com
.talkonly.net
||talkonly.net
||tamiaode.tk
||tanc.org
tangben.com
.tangren.us
.taoism.net
|http://taoism.net
.taolun.info
||taolun.info
.tapatalk.com
||tapatalk.com
blog.taragana.com
.tascn.com.au
||taup.net
|http://www.taup.org.tw
.taweet.com
||taweet.com
.tbcollege.org
||tbcollege.org
.tbi.org.hk
.tbicn.org
.tbjyt.org
||tbpic.info
.tbrc.org
tbs-rainbow.org
.tbsec.org
||tbsec.org
tbskkinabalu.page.tl
.tbsmalaysia.org
.tbsn.org
||tbsn.org
.tbsseattle.org
.tbssqh.org
|http://tbssqh.org
tbswd.org
.tbtemple.org.uk
.tbthouston.org
.tccwonline.org
.tcewf.org
tchrd.org
tcnynj.org
||tcpspeed.co
.tcpspeed.com
||tcpspeed.com
.tcsofbc.org
.tcsovi.org
.tdm.com.mo
teamamericany.com
||techspot.com
!--OVH
||techviz.net
||teck.in
.teeniefuck.net
teensinasia.com
||tehrantimes.com
.telecomspace.com
||telegraph.co.uk
.tenacy.com
||tenor.com
||tenzinpalmo.com
.tew.org
||tew.org
||tfiflve.com
.thaicn.com
||theatlantic.com
||theatrum-belli.com
||cn.theaustralian.com.au
theblemish.com
||thebcomplex.com
||theblaze.com
.thebobs.com
||thebobs.com
.thechinabeat.org
||thechinacollection.org
|http://www.thechinastory.org/yearbooks/yearbook-2012/
||theconversation.com
.thedalailamamovie.com
|http://thedalailamamovie.com
||thediplomat.com
||thedw.us
||theepochtimes.com
!--||thefreeland.club
thefrontier.hk/tf
||theguardian.com
||thegay.com
|http://thegioitinhoc.vn/
.thegly.com
.thehots.info
thehousenews.com
||thehun.net
.theinitium.com
||theinitium.com
||themoviedb.org
.thenewslens.com
||thenewslens.com
.thepiratebay.org
||thepiratebay.org
!--||thepiratebay.se
.theporndude.com
||theporndude.com
||theportalwiki.com
||theprint.in
||threadreaderapp.com
thereallove.kr
therock.net.nz
||thesaturdaypaper.com.au
||thestandnews.com
thetibetcenter.org
thetibetconnection.org
.thetibetmuseum.org
.thetibetpost.com
||thetibetpost.com
!--Tor
||thetinhat.com
thetrotskymovie.com
||thetvdb.com
thevivekspot.com
||thewgo.org
.theync.com
|http://theync.com
.thinkingtaiwan.com
||thinkingtaiwan.com
.thisav.com
|http://thisav.com
.thlib.org
||thomasbernhard.org
.thongdreams.com
threatchaos.com
||throughnightsfire.com
.thumbzilla.com
||thywords.com
.thywords.com.tw
tiananmenmother.org
.tiananmenduizhi.com
||tiananmenduizhi.com
||tiananmenuniv.com
||tiananmenuniv.net
||tiandixing.org
.tianhuayuan.com
.tianlawoffice.com
||tianti.io
tiantibooks.org
||tiantibooks.org
tianyantong.org.cn
.tianzhu.org
.tibet.at
tibet.ca
.tibet.com
||tibet.com
tibet.fr
.tibet.net
||tibet.net
||tibet.nu
.tibet.org
||tibet.org
.tibet.sk
||tibet.org.tw
||tibet.to
.tibet-envoy.eu
||tibet-envoy.eu
.tibet-foundation.org
.tibet-house-trust.co.uk
||tibet-initiative.de
.tibet-munich.de
.tibet3rdpole.org
|http://tibet3rdpole.org
tibetaction.net
||tibetaction.net
.tibetaid.org
tibetalk.com
.tibetan.fr
tibetan-alliance.org
.tibetanarts.org
.tibetanbuddhistinstitute.org
||tibetanbuddhistinstitute.org
||tibetancommunity.org
||tibetanentrepreneurs.org
||tibetanhealth.org
.tibetanjournal.com
.tibetanlanguage.org
.tibetanliberation.org
||tibetanliberation.org
.tibetcollection.com
.tibetanaidproject.org
.tibetancommunityuk.net
|http://tibetancommunityuk.net
tibetanculture.org
tibetanfeministcollective.org
.tibetanpaintings.com
.tibetanphotoproject.com
.tibetanpoliticalreview.org
.tibetanreview.net
|http://tibetansports.org
.tibetanwomen.org
|http://tibetanwomen.org
.tibetanyouth.org
.tibetanyouthcongress.org
||tibetanyouthcongress.org
.tibetcharity.dk
tibetcharity.in
.tibetchild.org
.tibetcity.com
||tibetcorps.org
||tibetexpress.net
||tibetfocus.com
||tibetfund.org
.tibetgermany.com
||tibetgermany.de
.tibethaus.com
.tibetheritagefund.org
||tibethouse.jp
||tibethouse.org
||tibethouse.us
.tibetinfonet.net
.tibetjustice.org
.tibetkomite.dk
||tibetmuseum.org
||tibetnetwork.org
||tibetoffice.ch
tibetoffice.eu
||tibetoffice.org
||tibetonline.com
||tibetoffice.com.au
||tibetonline.tv
||tibetoralhistory.org
||tibetpolicy.eu
||tibetrelieffund.co.uk
||tibetsites.com
||tibetsociety.com
||tibetsun.com
||tibetsupportgroup.org
||tibetswiss.ch
||tibettelegraph.com
||tibettimes.net
||tibettruth.com
||tibetwrites.org
.ticket.com.tw
.tigervpn.com
||tigervpn.com
.timdir.com
|http://timdir.com
.time.com
|http://time.com
!--.time.com/time/time100/leaders/profile/rebel
!--.time.com/time/specials/packages/article/0,28804
!--.time.com/time/magazine
||timesnownews.com
.timsah.com
||timtales.com
||blog.tiney.com
tintuc101.com
.tiny.cc
|http://tiny.cc
tinychat.com
||tinypaste.com
||tipas.net
.tistory.com
||tkcs-collins.com
.tmagazine.com
||tmagazine.com
.tmdfish.com
|http://tmi.me
.tmpp.org
|http://tmpp.org
.tnaflix.com
||tnaflix.com
.tngrnow.com
.tngrnow.net
.tnp.org
|http://tnp.org
.to-porno.com
||to-porno.com
togetter.com
.tokyo-247.com
.tokyo-hot.com
||tokyo-porn-tube.com
||tokyocn.com
tw.tomonews.net
.tongil.or.kr
.tono-oka.jp
tonyyan.net
.toodoc.com
toonel.net
top81.ws
.topnews.in
.toppornsites.com
|http://toppornsites.com
.torguard.net
||torguard.net
||top.tv
.topshareware.com
.topsy.com
||topsy.com
||toptip.ca
tora.to
.torcn.com
||torlock.com
.torproject.org
||torproject.org
||torrentkitty.tv
torrentprivacy.com
||torrentprivacy.com
|http://torrentproject.se
||torrenty.org
||torrentz.eu
||tortoisesvn.net
||torvpn.com
||totalvpn.com
.toutiaoabc.com
towngain.com
toypark.in
toytractorshow.com
.tparents.org
.tpi.org.tw
||tpi.org.tw
||tradingview.com
||transparency.org
||treemall.com.tw
trendsmap.com
||trendsmap.com
.trialofccp.org
||trialofccp.org
.trimondi.de/SDLE
.trouw.nl
||trouw.nl
.trt.net.tr
||trt.net.tr
trtc.com.tw
.truebuddha-md.org
||truebuddha-md.org
trulyergonomic.com
.truth101.co.tv
||truth101.co.tv
.truthontour.org
||truthontour.org
||truthsocial.com
.truveo.com
.tsctv.net
.tsemtulku.com
tsquare.tv
.tsu.org.tw
tsunagarumon.com
!--|http://www.tsuru-bird.net/
.tsctv.net
||tt1069.com
.tttan.com
||tttan.com
||ttv.com.tw
tu8964.com
.tubaholic.com
.tube.com
tube8.com
||tube8.com
.tube911.com
||tube911.com
.tubecup.com
.tubegals.com
.tubeislam.com
|http://tubeislam.com
.tubestack.com
||tubewolf.com
.tuibeitu.net
tuidang.net
.tuidang.org
||tuidang.org
.tuidang.se
bbs.tuitui.info
.tumutanzi.com
|http://tumutanzi.com
||tumview.com
.tunein.com
|http://tunein.com
||tunnelbear.com
||tunnelblick.net
.tunnelr.com
||tunnelr.com
||tunsafe.com
tuitwit.com
.turansam.org
.turbobit.net
||turbobit.net
.turbohide.com
||turbohide.com
||turkistantimes.com
.tushycash.com
|http://tushycash.com
||app.tutanota.com
.tuvpn.com
||tuvpn.com
|http://tuzaijidi.com
|http://*.tuzaijidi.com
.tw01.org
|http://tw01.org
!---Tumblr---
.tumblr.com
||tumblr.com
!--@@||assets.tumblr.com
!--@@||data.tumblr.com
!--@@||media.tumblr.com
!--@@||static.tumblr.com
!--@@||www.tumblr.com
||lecloud.net
|http://cosmic.monar.ch
||slutmoonbeam.com
|http://blog.soylent.com
.tv.com
|http://tv.com
tvants.com
forum.tvb.com
news.tvb.com/list/world
news.tvb.com/local
news.tvbs.com.tw
.tvboxnow.com
|http://tvboxnow.com/
tvider.com
.tvmost.com.hk
.tvplayvideos.com
||tvunetworks.com
.tw-blog.com
|https://tw-blog.com
.tw-npo.org
.twaitter.com
twapperkeeper.com
||twapperkeeper.com
||twaud.io
.twaud.io
.twavi.com
.twbbs.net.tw
twbbs.org
twbbs.tw
||twblogger.com
tweepmag.com
.tweepml.org
||tweepml.org
.tweetbackup.com
||tweetbackup.com
tweetboard.com
||tweetboard.com
.tweetboner.biz
||tweetboner.biz
.tweetcs.com
|http://tweetcs.com
|http://deck.ly
!-- Operation discontinued
!--||tweete.net
!--m.tweete.net
||mtw.tl
||tweetedtimes.com
!-- Operation discontinued
!--tweetmeme.com
||tweetmylast.fm
tweetphoto.com
||tweetphoto.com
||tweetrans.com
tweetree.com
||tweetree.com
.tweettunnel.com
||tweettunnel.com
||tweetwally.com
tweetymail.com
||twelve.today
.tweez.net
|http://tweez.net
||twftp.org
||twgreatdaily.com
twibase.com
.twibble.de
||twibble.de
twibbon.com
||twibs.com
.twicountry.org
|http://twicountry.org
twicsy.com
.twiends.com
|http://twiends.com
.twifan.com
|http://twifan.com
twiffo.com
||twiffo.com
.twilightsex.com
twilog.org
twimbow.com
||twindexx.com
twipple.jp
||twipple.jp
||twip.me
twishort.com
||twishort.com
twistar.cc
||twister.net.co
||twisterio.com
twisternow.com
twistory.net
twitbrowser.net
||twitcause.com
||twitgether.com
||twiggit.org
twitgoo.com
twitiq.com
||twitiq.com
.twitlonger.com
||twitlonger.com
|http://tl.gd/
twitmania.com
twitoaster.com
||twitoaster.com
||twitonmsn.com
!--Same IP
.twit2d.com
||twit2d.com
.twitstat.com
||twitstat.com
||firstfivefollowers.com
||retweeteffect.com
||tweeplike.me
||tweepguide.com
||turbotwitter.com
.twitvid.com
||twitvid.com
|http://twt.tl
twittbot.net
||ads-twitter.com
||twttr.com
||twitter4j.org
.twittercounter.com
||twittercounter.com
twitterfeed.com
.twittergadget.com
||twittergadget.com
.twitterkr.com
||twitterkr.com
||twittermail.com
||twitterrific.com
twittertim.es
||twittertim.es
twitthat.com
||twitturk.com
.twitturly.com
||twitturly.com
.twitzap.com
twiyia.com
||twstar.net
.twtkr.com
|http://twtkr.com
.twnorth.org.tw
||twreporter.org
twskype.com
twtrland.com
twurl.nl
.twyac.org
||twyac.org
.txxx.com
.tycool.com
||tycool.com
!--typepad
||typepad.com
@@||www.typepad.com
@@||static.typepad.com
||blog.expofutures.com
||legaltech.law.com
||blogs.tampabay.com
||contests.twilio.com
!-lawprofessors.typepad.com/china_law_prof
||typora.io
!--------------------UU-------------------------
.u9un.com
||u9un.com
.ubddns.org
|http://ubddns.org
||uberproxy.net
.uc-japan.org
||uc-japan.org
.srcf.ucam.org/salon/
|http://china.ucanews.com/
||ucdc1998.org
|http://hum*.uchicago.edu/faculty/ywang/history
||uderzo.it
.udn.com
||udn.com
||udn.com.tw
udnbkk.com/bbs
||uforadio.com.tw
ufreevpn.com
.ugo.com
!--ghs
||uhdwallpapers.org
||uhrp.org
.uighur.nl
||uighur.nl
uighurbiz.net
.ulike.net
ukcdp.co.uk
ukliferadio.co.uk
||ukliferadio.co.uk
ultravpn.fr
||ultravpn.fr
ultraxs.com
umich.edu/~falun
||unblock.cn.com
.unblocker.yt
unblock-us.com
||unblock-us.com
.unblockdmm.com
|http://unblockdmm.com
||unblocksit.es
uncyclomedia.org
.uncyclopedia.hk/wiki
|http://uncyclopedia.hk
!--uncyclopedia.info
|http://uncyclopedia.tw
underwoodammo.com
||underwoodammo.com
||unholyknight.com
.uni.cc
||cldr.unicode.org
.unification.net
.unification.org.tw
||unirule.cloud
.unitedsocialpress.com
.unix100.com
||unknownspace.org
.unodedos.com
unpo.org
||unstable.icu
.untraceable.us
|http://untraceable.us
||uocn.org
tor.updatestar.com
||upghsbc.com
.upholdjustice.org
.upload4u.info
uploaded.net/file
|http://uploaded.net/file
|http://uploaded.to/file
.uploadstation.com/file
.upmedia.mg
||upmedia.mg
.upornia.com
|http://upornia.com
||uproxy.org
||uptodown.com
.upwill.org
ur7s.com
||urbandictionary.com
||urbansurvival.com
myshare.url.com.tw/
||urlborg.com
||urlparser.com
us.to
||usacn.com
.usaip.eu
||usaip.eu
||uscnpm.org
||uscardforum.com
||usma.edu
.usocctn.com
||ustibetcommittee.org
.ustream.tv
||ustream.tv
usus.cc
.utopianpal.com
||utopianpal.com
.uu-gg.com
.uvwxyz.xyz
||uvwxyz.xyz
.uwants.com
||uwants.com
.uwants.net
uyghur.co.uk
|http://uyghur-j.org
||uyghuraa.org
||uyghuramerican.org
||uyghurbiz.org
||uyghurcanadian.ca
||uyghurcongress.org
||uyghurpen.org
||uyghurpress.com
||uyghurstudies.org
||uyghurtribunal.com
uygur.org
|http://uymaarip.com/
!--------------------VV-------------------------
||v2fly.org
.v2ray.com
||v2ray.com
||v2raycn.com
||v2raytech.com
||valeursactuelles.com
.van001.com
.van698.com
.vanemu.cn
.vanilla-jp.com
.vanpeople.com
vansky.com
||vaticannews.va
||vcf-online.org
||vcfbuilder.org
.vegasred.com
.velkaepocha.sk
.venbbs.com
.venchina.com
.venetianmacao.com
||venetianmacao.com
veoh.com
||vercel.app
mysite.verizon.net
vermonttibet.org
.versavpn.com
||versavpn.com
||verybs.com
.vft.com.tw
.viber.com
||viber.com
.vica.info
.victimsofcommunism.org
||victimsofcommunism.org
||vid.me
||vidble.com
videobam.com
||videobam.com
.videodetective.com
.videomega.tv
||videomega.tv
.videomo.com
videopediaworld.com
.videopress.com
.vidinfo.org/video
vietdaikynguyen.com
.vijayatemple.org
||vilavpn.com
vimeo.com
||vimeo.com
||vimperator.org
||vincnd.com
||vinniev.com
|http://www.lib.virginia.edu/area-studies/Tibet/tibet.html
.virtualrealporn.com
||virtualrealporn.com
visibletweets.com
|http://ny.visiontimes.com
.vital247.org
||viu.com
.vivahentai4u.net
||vivaldi.com
.vivatube.com
.vivthomas.com
||vivthomas.com
.vjav.com
||vjav.com
.vjmedia.com.hk
.vllcs.org
|http://vllcs.org
||vmixcore.com
||vnet.link
.vocativ.com
vocn.tv
||vocus.cc
||voicettank.org
.vot.org
||vot.org
.vovo2000.com
|http://vovo2000.com
.voxer.com
||voxer.com
.voy.com
||vpn.ac
.vpn4all.com
||vpn4all.com
.vpnaccount.org
|http://vpnaccount.org
.vpnaccounts.com
||vpnaccounts.com
.vpncomparison.org
.vpncup.com
||vpncup.com
vpnbook.com
.vpncoupons.com
|http://vpncoupons.com
.vpndada.com
||vpndada.com
.vpnfan.com
vpnfire.com
.vpnfires.biz
.vpnforgame.net
||vpnforgame.net
||vpngate.jp
.vpngate.net
||vpngate.net
.vpngratis.net
vpnhq.com
||vpnhub.com
.vpnmaster.com
||vpnmaster.com
.vpnmentor.com
||vpnmentor.com
.vpninja.net
||vpninja.net
.vpnintouch.com
||vpnintouch.net
vpnjack.com
||vpnjack.com
.vpnpick.com
||vpnpick.com
||vpnpop.com
||vpnpronet.com
.vpnreactor.com
||vpnreactor.com
||vpnreviewz.com
.vpnsecure.me
||vpnsecure.me
.vpnshazam.com
||vpnshazam.com
.vpnshieldapp.com
||vpnshieldapp.com
.vpnsp.com
.vpntraffic.com
.vpntunnel.com
||vpntunnel.com
.vpnuk.info
||vpnuk.info
||vpnunlimitedapp.com
.vpnvip.com
||vpnvip.com
.vpnworldwide.com
.vporn.com
||vporn.com
.vpser.net
@@||vpser.net
vraiesagesse.net
||vrchat.com
.vrmtr.com
||vtunnel.com
||vuku.cc
!--------------------WW-------------------------
lists.w3.org/archives/public
||w3schools.com
||waffle1999.com
.wahas.com
.waigaobu.com
waikeung.org/php_wind
.wailaike.net
||wainao.me
.waiwaier.com
|http://waiwaier.com
||wallmama.com
wallornot.org
||wallpapercasa.com
.wallproxy.com
@@||wallproxy.com.cn
||wallsttv.com
||waltermartin.com
||waltermartin.org
||www.wan-press.org
||wanderinghorse.net
||wangafu.net
||wangjinbo.org
.wangjinbo.org
wanglixiong.com
.wango.org
||wango.org
wangruoshui.net
www.wangruowang.org
||want-daily.com
wapedia.mobi/zhsimp
||warroom.org
||waselpro.com
.watchinese.com
||watchout.tw
.wattpad.com
||wattpad.com
.makzhou.warehouse333.com
washeng.net
.watch8x.com
||watchmygf.net
||wav.tv
||wd.bible
.wdf5.com
||wealth.com.tw
.wearehairy.com
.wearn.com
||wearn.com
|http://hkcoc.weather.com.hk
||hudatoriq.web.id
||web2project.net
webbang.net
.webevader.org
.webfreer.com
weblagu.com
.webjb.org
.webrush.net
webs-tv.net
.websitepulse.com/help/testtools.china-test
|http://www.websnapr.com
.webwarper.net
|http://webwarper.net
webworkerdaily.com
||wechatlawsuit.com
.weekmag.info
||wefightcensorship.org
.wefong.com
weiboleak.com
.weihuo.org
||weijingsheng.org
.weiming.info
||weiming.info
weiquanwang.org
|http://weisuo.ws
.welovecock.com
||welt.de
.wemigrate.org
|http://wemigrate.org
wengewang.com
||wengewang.org
.wenhui.ch
|http://trans.wenweipo.com/gb/
.wenxuecity.com
||wenxuecity.com
.wenyunchao.com
||wenyunchao.com
.westca.com
||westca.com
||westernwolves.com
.westkit.net
||westpoint.edu
.westernshugdensociety.org
wetpussygames.com
.wetplace.com
wexiaobo.org
||wexiaobo.org
wezhiyong.org
||wezone.net
.wforum.com
||wforum.com/
.whatblocked.com
||whatblocked.com
.wheatseeds.org
||wheelockslatin.com
.whippedass.com
!--|http://who.is/
.whoer.net
||whoer.net
whotalking.com
whylover.com
||whyx.org
||wikileaks.ch
||wikileaks.com
||wikileaks.de
||wikileaks.eu
||wikileaks.lu
.wikileaks.org
||wikileaks.org
||wikileaks.pl
.wikileaks-forum.com
wildammo.com
.williamhill.com
||collateralmurder.com
||collateralmurder.org
wikilivres.info/wiki/%E9%9B%B6%E5%85%AB%E5%AE%AA%E7%AB%A0
||wikimapia.org
.wikiwand.com
||wikiwand.com
||wikiwiki.jp
||casino.williamhill.com
||sports.williamhill.com
||vegas.williamhill.com
||willw.net
||windowsphoneme.com
.windscribe.com
||windscribe.com
||community.windy.com
||wingy.site
.winning11.com
winwhispers.info
||wionews.com
||wiredbytes.com
||wiredpen.com
||wireguard.com
!--||wireshark.org
.wisdompubs.org
.wisevid.com
||wisevid.com
||whispersystems.org
.witnessleeteaching.com
.witopia.net
.wjbk.org
||wjbk.org
||wmflabs.org
||wn.com
.wnacg.com
.wnacg.org
.wo.tc
||woeser.com
.wokar.org
||wokar.org
wolfax.com
||wolfax.com
||wombo.ai
||woolyss.com
woopie.jp
||woopie.jp
woopie.tv
||woopie.tv
||workatruna.com
.workerdemo.org.hk
.workerempowerment.org
||workers.dev
||workersthebig.net
.worldcat.org
worldjournal.com
.worldvpn.net
||worldvpn.net
||videopress.com
.wordpress.com
|http://*.wordpress.com
||chenshan20042005.wordpress.com
||chinaview.wordpress.com
||cnbbnews.wordpress.com
||freedominfonetweb.wordpress.com
||hka8964.wordpress.com
||hkanews.wordpress.com
||hqsbnet.wordpress.com
||hqsbonline.wordpress.com
||investigating.wordpress.com
||jobnewera.wordpress.com
||matthewdgreen.wordpress.com
||minghuiyw.wordpress.com
||wo3ttt.wordpress.com
||sujiatun.wordpress.com
||xijie.wordpress.com
||wp.com
!-||wormsculptor.com
.wow.com
.wow-life.net
||wowlegacy.ml
||wowporn.com
||wowgirls.com
.wowrk.com
woxinghuiguo.com
.woyaolian.org
|http://woyaolian.org
.wpoforum.com
||wpoforum.com
.wqyd.org
||wqyd.org
wrchina.org
wretch.cc
||writesonic.com
.wsj.com
||wsj.com
.wsj.net
||wsj.net
.wsjhk.com
.wtbn.org
.wtfpeople.com
wuerkaixi.com
||wufafangwen.com
||wufi.org.tw
||wuguoguang.com
wujie.net
wujieliulan.com
||wujieliulan.com
wukangrui.net
||wuw.red
||wuyanblog.com
.wwitv.com
||wwitv.com
wzyboy.im/post/160
!--------------------XX-------------------------
||x.co
.x-berry.com
||x-berry.com
||x-art.com
||x-wall.org
x1949x.com
x365x.com
xanga.com
||xbabe.com
.xbookcn.com
||xbookcn.com
||xcafe.in
||xcity.jp
.xcritic.com
|http://cdn*.xda-developers.com
.xerotica.com
destiny.xfiles.to/ubbthreads
.xfm.pp.ru
.xgmyd.com
||xgmyd.com
xhamster.com
||xhamster.com
.xianba.net
.xianchawang.net
.xianjian.tw
|http://xianjian.tw
.xianqiao.net
.xiaobaiwu.com
.xiaochuncnjp.com
.xiaod.in
.xiaohexie.com
||xiaolan.me
||xiaoma.org
||xiaohexie.com
||xiaxiaoqiang.net
xiezhua.com
.xihua.es
forum.xinbao.de/forum
.xing.com
|http://xing.com
||xinjiangpolicefiles.org
.xinmiao.com.hk
||xinmiao.com.hk
xinsheng.net
xinshijue.com
xinhuanet.org
|http://xinyubbs.net
.xiongpian.com
.xiuren.org
||xixicui.icu
xizang-zhiye.org
xjp.cc
||xjp.cc
||xjtravelguide.com
xlfmtalk.com
||xlfmwz.info
||xml-training-guide.com
xmovies.com
||xnxx.com
!--||xnxx-cdn.com
xpdo.net
||xpud.org
.xrentdvd.com
.xskywalker.net
||xtube.com
blog.xuite.net
vlog.xuite.net
xuzhiyong.net
||xuchao.org
xuchao.net
||xuchao.net
xvideo.cc
.xvideos.com
||xvideos.com
||xvideos-cdn.com
||xvideos.es
||xvbelink.com
||xvinlink.com
.xkiwi.tk/
||xsden.info
.xxbbx.com
.xxlmovies.com
||xxx.com
.xxx.xxx
|http://xxx.xxx
.xxxfuckmom.com
||xxxx.com.au
.xxxymovies.com
|http://xxxymovies.com
xys.org
xysblogs.org
xyy69.com
xyy69.info
!--------------------YY-------------------------
||y2mate.com
||yadi.sk
||yakbutterblues.com
||yam.com
||yam.org.tw
||yande.re
||disk.yandex.com
||disk.yandex.ru
.yanghengjun.com
yangjianli.com
.yasni.co.uk
||yasni.co.uk
!--||yasukuni.or.jp
.yayabay.com/forum
||news.ycombinator.com
.ydy.com
.yeahteentube.com
||yeahteentube.com
||yecl.net
||yeelou.com
||yeeyi.com
yegle.net
||yegle.net
.yes.xxx
||yes123.com.tw
||yesasia.com
||yesasia.com.hk
.yes-news.com
|http://yes-news.com
.yespornplease.com
||yespornplease.com
|http://yeyeclub.com
!--yfrog.com
||yhcw.net
.yibada.com
.yibaochina.com
.yidio.com
||yidio.com
||yigeni.com
yilubbs.com
||s.yimg.com
||xa.yimg.com
.yingsuoss.com
.yipub.com
||yipub.com
yinlei.org/mt
.yizhihongxing.com
||yizhihongxing.com
.yobt.com
.yobt.tv
||yobt.tv
.yogichen.org
||yogichen.org
.yolasite.com
.yomiuri.co.jp
yong.hu
.yorkbbs.ca
||you.com
||youxu.info
.youjizz.com
||youjizz.com
.youmaker.com
||youmaker.com
.youngpornvideos.com
youngspiration.hk
.youpai.org
||youpai.org
.your-freedom.net
||yourepeat.com
.yourprivatevpn.com
||yourprivatevpn.com
.yousendit.com
||yousendit.com
||youthforfreechina.org
.youthnetradio.org/tmit/forum
blog.youthwant.com.tw
me.youthwant.com.tw
share.youthwant.com.tw
topic.youthwant.com.tw
.youporn.com
||youporn.com
.youporngay.com
||youporngay.com
.yourlisten.com
||yourlisten.com
.yourlust.com
||yourlust.com
youshun12.com
.youtubecn.com
youversion.com
||youversion.com
ytht.net
yuanming.net
.yuanzhengtang.org
.yulghun.com
||yulghun.com
||yunchao.net
.yuvutu.com
||yvesgeleyn.com
.ywpw.com/forums/history/post/A0/p0/html/227
yx51.net
.yyii.org
||yyii.org
||yyjlymb.xyz
||yysub.net
.yzzk.com
||yzzk.com
!--------------------ZZ-------------------------
||z-lib.io
||z-lib.org
zacebook.com
.zalmos.com
||zalmos.com
||zannel.com
.zaobao.com
||zaobao.com
||zaobao.com.sg
.zaozon.com
||zdnet.com.tw
.zello.com
||zello.com
.zengjinyan.org
.zenmate.com
||zenmate.com
||zenmate.com.ru
||zerohedge.com
||zeronet.io
||zeutch.com
!--www.zfreet.com/post/usejump-browns.html
.zfreet.com
.zgsddh.com
zgzcjj.net
.zhanbin.net
||zhanbin.net
.zhangboli.net
||zhangtianliang.com
||zhanlve.org
zhenghui.org
.zhengjian.org
||zhengjian.org
zhengwunet.org
zhenlibu.info
||zhenlibu.info
.zhenlibu1984.com
||zhenlibu1984.com
|http://zhenxiang.biz
.zhinengluyou.com
zhongguo.ca
|http://zhongguorenquan.org
zhongguotese.net
||zhongguotese.net
||zhongmeng.org
.zhoushuguang.com
||zhreader.com
.zhuangbi.me
||zhuangbi.me
.zhuanxing.cn
||zhuatieba.com
zhuichaguoji.org
||zhuichaguoji.org
||zi.media
|http://book.zi5.me
.ziddu.com/download
||zillionk.com
.zinio.com
||zinio.com
.ziporn.com
.zippyshare.com
.zkaip.com
||zkaip.com
realforum.zkiz.com
!--||zlib.net
||zmw.cn
.zodgame.us
zomobo.net
.zonaeuropa.com
||zonaeuropa.com
||zonghexinwen.com
.zonghexinwen.net
||zoogvpn.com
||zootool.com
.zoozle.net
||zophar.net
writer.zoho.com
||zorrovpn.com
||zpn.im
||zspeeder.me
.zsrhao.com
.zuo.la
||zuo.la
||zuobiao.me
.zuola.com
||zuola.com
||zvereff.com
||zyxel.com
.zynaima.com
zyzc9.com
.zzcartoon.com
!##############General List End#################
!###########Supplemental List Start#############
!-----------------URL Keywords------------------
64memo
aHR0cHM6Ly95ZWNsLm5ldA
freenet
.google.*/falun
phobos.apple.com*/video
q=freedom
q%3Dfreedom
remembering_tiananmen_20_years
search*safeweb
q=triangle
q%3DTriangle
ultrareach
ultrasurf
!#############Supplemental List End#############
!################Whitelist Start################
@@||aliyun.com
@@||baidu.com
!--@@||bing.com
@@||chinaso.com
@@||chinaz.com
@@|http://nrch.culture.tw/
!---Some are powered by GuXiang (BGP), please comment off if
!---you encounter connectivity issues.
@@||adservice.google.com
!--ISP cache works sometimes, verified at drpeng + gehua.
@@||dl.google.com
!--@@||kh.google.com
!--@@||khm.google.com
!--@@||khm0.google.com
!--@@||khm1.google.com
!--@@||khm2.google.com
!--@@||khm3.google.com
!--@@||khmdb.google.com
@@||tools.google.com
@@||clientservices.googleapis.com
@@||fonts.googleapis.com
!--@@||khm.googleapis.com
!--@@||khm0.googleapis.com
!--@@||khm1.googleapis.com
!--@@||khm2.googleapis.com
!--@@||khm3.googleapis.com
!--@@||khmdb.googleapis.com
@@||storage.googleapis.com
!--@@||translate.googleapis.com
@@||update.googleapis.com
@@||safebrowsing.googleapis.com
@@||cn.gravatar.com
!--@@||connectivitycheck.gstatic.com
!--@@||csi.gstatic.com
!--@@||fonts.gstatic.com
!--@@||ssl.gstatic.com
@@||haosou.com
@@||ip.cn
@@||jike.com
@@|http://translate.google.cn
@@|http://www.google.cn/maps
@@||http2.golang.org
@@||gov.cn
@@||ocsp.pki.goog
@@||qq.com
@@||sina.cn
@@||sina.com.cn
@@||sogou.com
@@||so.com
@@||soso.com
@@||uluai.com.cn
@@||weibo.com
@@||yahoo.cn
@@||youdao.com
@@||zhongsou.com
@@|http://ime.baidu.jp
!################Whitelist End##################
!---------------------EOF-----------------------
================================================
FILE: packages/gui/extra/proxy/domestic-domain-allowlist.txt
================================================
[SwitchyOmega Conditions]
; Require: SwitchyOmega >= 2.3.2
; Update Date: 2024/12/01
; Author: Pluwen
; Usage: https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage
; IP 地址段
10.*.*.*
100.64.*.*
127.*.*.*
172.16.*.*
192.168.*.*
; cn 域名都不走代理
*.cn
; 其他域名
*.00cdn.com
*.0daydown.com
*.0o0.ooo
*.10010.com
*.10086cloud.com
*.114la.com
*.114yygh.com
*.115.com
*.123pan.com
*.126.com
*.126.net
*.127.net
*.139.com
*.163.com
*.163yun.com
*.1688.com
*.17173.com
*.178.com
*.17ce.com
*.17font.com
*.17k.com
*.199it.com
*.1ptba.com
*.1qimg.com
*.1qmsg.com
*.1tpic.com
*.1year.cc
*.1years.cc
*.21cn.com
*.21tb.com
*.2345.com
*.2cto.com
*.3322.cc
*.3366.com
*.33ss.tech
*.360.com
*.360buy.com
*.360buyimg.com
*.360doc.com
*.360in.com
*.360safe.com
*.36kr.com
*.39.net
*.3dmgame.com
*.4399.com
*.51.la
*.51.net
*.5173.com
*.5173cdn.com
*.51cto.com
*.51job.com
*.51ym.me
*.52audio.com
*.52yuwan.com
*.56.com
*.58.com
*.58pic.com
*.591mogu.com
*.616pic.com
*.699pic.com
*.71.am
*.7k7k.com
*.8686c.com
*.86ps.com
*.91.com
*.91118.com
*.91mjw.com
*.99.com
*.a9vg.com
*.aaplimg.com
*.abchina.com
*.accuweather.com
*.acfun.tv
*.acg.rip
*.acg.tv
*.acggate.net
*.acgvideo.com
*.acs.org
*.aday01.com
*.adf.ly
*.agora.io
*.aicdn.com
*.aicheren.com
*.aicoinstorge.com
*.aipai.com
*.air-matters.com
*.air-matters.io
*.airbnb.com
*.aiwebcom.com
*.aixifan.com
*.aizhan.com
*.akadns.net
*.akamaihd.net
*.akamaized.net
*.akarin.me
*.akarin.top
*.aldwx.com
*.aliapp.org
*.alibaba-inc.com
*.alibaba.com
*.alibabacloud.com
*.alibabausercontent.com
*.alicdn.com
*.alicloudccp.com
*.alikunlun.com
*.alikunlun.net
*.alimama.com
*.alipan.com
*.alipay.com
*.alipayobjects.com
*.alisports.com
*.aliued.com
*.aliyun.com
*.aliyuncs.com
*.aliyundrive.com
*.aliyunpds.com
*.allhistory.com
*.alltuu.com
*.amap.com
*.amd.com
*.ancda.com
*.animebytes.tv
*.anjuke.com
*.anquan.org
*.ant.design
*.antfin-inc.com
*.antfin.com
*.antpcdn.com
*.anw.red
*.anyway.fm
*.anzhi.com
*.appclub.in
*.appgame.com
*.appinn.com
*.appinn.net
*.apple-cloudkit.com
*.apple.co
*.apple.com
*.appletuan.com
*.appstore.com
*.aps.org
*.archlinux.org
*.archlinuxcn.org
*.areyoucereal.com
*.arubanetworks.com
*.atomicstryker.net
*.augix.me
*.autonavi.com
*.awesome-hd.me
*.axhub.im
*.axshare.com
*.axure.org
*.axureux.com
*.b612.net
*.babybus.com
*.baidu.com
*.baidubcr.com
*.baiducontent.com
*.baidupan.com
*.baidupcs.com
*.baidustatic.com
*.baiduwp.com
*.baiduyundns.com
*.baiduyundns.net
*.baimiaoapp.com
*.bankcomm.com
*.baomihua.com
*.baomitu.com
*.baozoumanhua.com
*.battle.net
*.bbtree.com
*.bcebos.com
*.bcedns.com
*.bcedns.net
*.bcy.net
*.bdatu.com
*.bdimg.com
*.bdstatic.com
*.bdydns.com
*.bdydns.net
*.behe.com
*.beianbeian.com
*.beisen.com
*.beitaichufang.com
*.bejson.com
*.bendibao.com
*.bible.com
*.biliapi.com
*.biliapi.net
*.bilibili.com
*.bilibili.tv
*.bilicomic.com
*.biligame.com
*.biligame.net
*.bilivideo.com
*.bitbucket.org
*.blackyau.cc
*.blizzard.com
*.blogchina.com
*.blogjava.net
*.bluedoc.io
*.booking.com
*.bootcss.com
*.bqtalk.com
*.broadcasthe.net
*.bstatic.com
*.bt0.com
*.btdx8.com
*.btsync.org
*.btyingshi.com
*.bumimi.com
*.bybbs.org
*.bytecdntp.com
*.ca001.com
*.cachemoment.com
*.cailianpress.com
*.caiyunapp.com
*.camera360.com
*.ccb.com
*.ccgslb.com
*.ccgslb.net
*.cckefu.net
*.cckefu3.com
*.cctv.com
*.cctvpic.com
*.cdn-apple.com
*.cdn.hockeyapp.net
*.cdnbee.com
*.cdndm.com
*.cdndm5.com
*.cdnjs.com
*.cdnst.net
*.cdntip.com
*.cdog.me
*.ceair.com
*.cebbank.com
*.cee.network
*.chainnews.com
*.chaoxing.com
*.chdbits.co
*.china.com
*.chinanetcenter.com
*.chinaso.com
*.chinassl.net
*.chinaunix.net
*.chinauos.com
*.chinaz.com
*.chiphell.com
*.chongdiantou.com
*.chuangzaoshi.com
*.chuimg.com
*.chunyu.mobi
*.chunyuanfood.com
*.ciligod.com
*.citicbank.com
*.classix-unlimited.co.uk
*.cli.im
*.clouddn.com
*.cloudinary.com
*.cloudxns.net
*.cmbchina.com
*.cmbimg.com
*.cn-ki.net
*.cn.engadget.com
*.cnbeta.com
*.cnbetacdn.com
*.cnblogs.com
*.cnki.net
*.cnsageo.com
*.cnzz.com
*.cnzz.net
*.code4app.com
*.coding.io
*.coding.me
*.coding.net
*.coloros.com
*.comicat.org
*.coolapk.com
*.coolpad.com
*.cootekservice.com
*.cowtransfer.com
*.cqvip.com
*.csair.com
*.csdn.net
*.css-js.com
*.css.net
*.css.network
*.ct10000.com
*.ctrip.com
*.cupfox.app
*.d7vg.com
*.damengxiang.me
*.dandanplay.com
*.dangdang.com
*.daocloud.io
*.datagrand.com
*.dbankcdn.com
*.ddos.cc
*.ddrk.me
*.deepin.com
*.deepin.org
*.deepinos.org
*.deliwenku.com
*.dfcfw.com
*.dgtle.com
*.dianping.com
*.didialift.com
*.didiglobal.com
*.dilidili.com
*.dilidili.wang
*.dingtalk.com
*.dingtalkapps.com
*.diybeta.com
*.diyvm.com
*.dji.com
*.dji.net
*.dm5.com
*.dmzj.com
*.dns.com
*.dnspao.com
*.doc88.com
*.docer.com
*.docin.com
*.docschina.org
*.dopa.com
*.douban.*
*.douban.com
*.douban.fm
*.doubanio.com
*.doubleclick.net
*.douyin.com
*.douyu.com
*.douyutv.com
*.doyoo.net
*.doyoudo.com
*.dpfile.com
*.draw.io
*.drivergenius.com
*.dsxys.com
*.duanwenxue.com
*.duguletian.com
*.duokan.com
*.duoshao.app
*.duoshao.net
*.duoshuo.com
*.duowan.com
*.dwstatic.com
*.dxycdn.com
*.dygod.net
*.dytt8.net
*.easou.com
*.eastmoney.com
*.ecitic.com
*.edifier.com
*.eebbk.com
*.eeboard.com
*.eggjs.org
*.ele.me
*.elemecdn.com
*.elong.com
*.elsevier.com
*.empornium.me
*.enkj.com
*.epicgames.com
*.epubw.com
*.erp321.com
*.etao.com
*.eudic.net
*.ewei.com
*.fang.com
*.fatetypo.xyz
*.feiliao.com
*.feishucdn.com
*.feng.com
*.fengkongcloud.com
*.fengniao.com
*.ffalcon.com
*.figma.cool
*.figmachina.com
*.figmacn.com
*.fiio.com
*.fir.im
*.firefox.com
*.fj12379.com
*.fjdzyz.com
*.fjgdwl.com
*.fjhxbank.com
*.fliggy.com
*.flomoapp.com
*.flow.ci
*.flyertea.com
*.fnnas.com
*.fontke.com
*.foundertype.com
*.foxirj.com
*.frdic.com
*.freebuf.com
*.freeziti.com
*.fromgeek.com
*.futu5.com
*.futunn.com
*.fydeos.com
*.fzzfgjj.com
*.g-cores.com
*.galstars.net
*.gamersky.com
*.gandi.net
*.ganji.com
*.gank.io
*.gazellegames.net
*.gcores.com
*.geetest.com
*.geilicdn.com
*.getfedora.org
*.getpricetag.com
*.getui.com
*.gfan.com
*.gifshow.com
*.gitee.com
*.gitee.io
*.godic.net
*.golaravel.com
*.goofish.com
*.googletagmanager.com
*.gratisography.com
*.growingio.com
*.gtimg.com
*.guazi.com
*.guokr.com
*.gwdang.com
*.h-ui.net
*.h2os.com
*.hacpai.com
*.haitum.com
*.halyul.cc
*.hao123.com
*.haosou.com
*.happyeo.com
*.harmonyos.com
*.hasee.com
*.hdb.com
*.hdbits.org
*.hdchina.org
*.hddolby.com
*.hdfans.org
*.hdhome.org
*.hdsky.me
*.hdslb.com
*.hdslb.net
*.hejie.me
*.heweather.com
*.hexun.com
*.hexunimg.com
*.hicloud.com
*.hihonor.com
*.hikvision.com
*.hitv.com
*.hiwifi.com
*.homestyler.com
*.hommk.com
*.hongxiu.com
*.honor.com
*.hostbuf.com
*.hostker.com
*.hotmail.com
*.houxu.app
*.huaban.com
*.huabanimg.com
*.huanmusic.com
*.huanqiu.com
*.huawei.com
*.huaweicloud.com
*.huiji.wiki
*.huijistatic.com
*.huijiwiki.com
*.hujiang.com
*.huomao.com
*.hupu.com
*.huxiu.com
*.huxiucdn.com
*.huya.com
*.hxcdn.net
*.hxjyb.com
*.hy233.tv
*.i-meto.com
*.iapps.im
*.iaweg.com
*.iaxure.com
*.ibm.com
*.ibruce.info
*.ibucm.com
*.icetorrent.org
*.iciba.com
*.icloud-content.com
*.icloud.com
*.idqqimg.com
*.ieee.org
*.iesdouyin.com
*.ifanr.com
*.ifanr.in
*.ifdream.net
*.ifeng.com
*.ifengimg.com
*.ifigma.design
*.igamecj.com
*.iguoguo.net
*.iguxuan.com
*.iina.io
*.ijinshan.com
*.iknoworld.net
*.iknowwhatyoudownload.com
*.im9.com
*.imiku.me
*.imooc.com
*.imququ.com
*.indienova.com
*.infinitynewtab.com
*.infoq.com
*.installbi.me
*.intercomcdn.com
*.ip-api.com
*.ip-cdn.com
*.ip.la
*.ip.sb
*.ip138.com
*.ipip.net
*.iplaysoft.com
*.ipv6-test.com
*.iqihang.com
*.iqing.in
*.iqiyi.com
*.iqiyipic.com
*.irs01.com
*.isharepc.com
*.it168.com
*.iteye.com
*.ithome.com
*.itjuzi.com
*.jandan.net
*.java.com
*.javaeye.com
*.jb51.net
*.jcodecraeer.com
*.jd.com
*.jd.hk
*.jdkindle.com
*.jdpay.com
*.jetbrains.com
*.jfdaily.com
*.jfrft.com
*.jhdec.com
*.jianguoyun.com
*.jianshu.*
*.jianshu.com
*.jianshu.io
*.jianshuapi.com
*.jiathis.com
*.jidian.im
*.jiemian.com
*.jikexueyuan.com
*.jikipedia.com
*.jinshuju.net
*.jisuanke.com
*.jomodns.com
*.joyneop.xyz
*.joyyang.com
*.jpopsuki.eu
*.jqhtml.com
*.js.design
*.jsdelivr.com
*.juejin.im
*.juji.tv
*.kaiyanapp.com
*.kan300.com
*.kankan.com
*.kanzhun.com
*.kaspersky-labs.com
*.kcdnvip.com
*.ke.com
*.keepcdn.com
*.keepfrds.com
*.kekenet.com
*.kele5240.com
*.kf5.com
*.kingsoft.com
*.kkmh.com
*.kmf.com
*.knewone.com
*.knownsec.com
*.ksosoft.com
*.ksyun.com
*.ksyungslb.com
*.ku6.com
*.kuaidi100.com
*.kuaishou.com
*.kuaizhan.com
*.kugou.com
*.kujiale.com
*.kunlunaq.com
*.kunlunar.com
*.kunlunca.com
*.kunluncan.com
*.kunlunea.com
*.kunlungem.com
*.kunlungr.com
*.kunlunhuf.com
*.kunlunle.com
*.kunlunli.com
*.kunlunno.com
*.kunlunpi.com
*.kunlunra.com
*.kunlunsa.com
*.kunlunsc.com
*.kunlunsl.com
*.kunlunso.com
*.kunlunta.com
*.kunlunvi.com
*.kunlunwe.com
*.kyoceraconnect.com
*.lackar.com
*.lagou.com
*.lanhuapp.com
*.lanjinger.com
*.lany.me
*.lanyus.com
*.lanzous.com
*.lanzoux.com
*.laravel-china.org
*.layui.com
*.lbesec.com
*.le.com
*.lecloud.com
*.leetcode-cn.com
*.lemicp.com
*.lenovo.net
*.lenovomobile.com
*.letv.com
*.letvimg.com
*.lianjia.com
*.liantu.com
*.liaoxuefeng.com
*.licdn.com
*.liepin.com
*.lifan.ooo
*.likefont.com
*.lilithgames.com
*.linuxidc.com
*.livechina.com
*.liyin.date
*.lizhi.fm
*.lizhi.io
*.lkkdesign.com
*.lncld.net
*.locoy.com
*.locvps.com
*.lofter.com
*.loj.ac
*.loli.net
*.lolinet.com
*.longzhu.com
*.lucifr.com
*.ludashi.com
*.luogu.org
*.luojilab.com
*.luoo.net
*.lvmama.com
*.lwl12.com
*.ly.com
*.lyjsws.com
*.m-team.cc
*.macpaw.com
*.macrr.com
*.macw.com
*.macwk.com
*.madsrevolution.net
*.magi.com
*.mail4geek.com
*.manmanbuy.com
*.maoyan.com
*.maoyun.tv
*.masadora.net
*.mastergo.com
*.maxfox.me
*.mcbbs.net
*.mdnice.com
*.mdui.org
*.me.com
*.mediav.com
*.megvii.com
*.meican.com
*.meiin.com
*.meijutw.com
*.meipai.com
*.meiqia.com
*.meitu.com
*.meituan.com
*.meituan.net
*.meitudata.com
*.meitustat.com
*.meixincdn.com
*.meizu.com
*.mengniang.org
*.mgtv.com
*.mi-img.com
*.mi.com
*.miaopai.com
*.microbit.org
*.midifan.com
*.mikanani.me
*.minapp.com
*.mindstore.io
*.mingdao.com
*.miui.com
*.miwifi.com
*.mls-cdn.com
*.mmstat.com
*.mmtrix.com
*.mob.com
*.mobike.com
*.moe.im
*.moe123.net
*.moegirl.org
*.moetransit.com
*.mojidoc.com
*.moke.com
*.mokeedev.com
*.momentcdn.com
*.momoyu.cc
*.moonvy.com
*.morethan.tv
*.mozilla.org
*.mp4ba.cc
*.msftconnecttest.com
*.mtyun.com
*.mu6.me
*.mubu.com
*.muchong.com
*.mukewang.com
*.mumayi.com
*.muscache.com
*.mxhichina.com
*.myanonamouse.net
*.myapp.com
*.mydrivers.com
*.myip.la
*.myqcloud.com
*.myzaker.com
*.mzstatic.com
*.naixue.com
*.nanyangpt.com
*.nature.com
*.ncore.cc
*.nekonazo.com
*.netease.com
*.netease.im
*.netseer.com
*.netspeedtestmaster.com
*.newsmth.net
*.ngacn.cc
*.nim-lang-cn.org
*.nipic.com
*.nlark.com
*.nobook.com
*.nocode.com
*.now.sh
*.nowcoder.com
*.nowcoder.net
*.ntp.org
*.nuomi.com
*.nvidia.com
*.nyato.com
*.obsapp.com
*.oekaki.so
*.office.net
*.office365.com
*.okii.com
*.omico.me
*.onekbit.com
*.oneplus.com
*.oneplusbbs.com
*.onlinedown.net
*.open-open.com
*.open.cd
*.oppo.com
*.ops.moe
*.oracle.com
*.oray.com
*.oray.net
*.orayimg.com
*.oschina.io
*.oschina.net
*.ourbits.club
*.ourdvs.com
*.ourdvsss.com
*.oursketch.com
*.outlook.com
*.pag.art
*.paipai.com
*.panda.tv
*.panduoduo.net
*.paperpass.com
*.passthepopcorn.me
*.pc6.com
*.pcbeta.com
*.pdcicons.ml
*.pdim.gs
*.pengyou.com
*.pexels.com
*.pgyer.com
*.phonegap100.com
*.phpcomposer.com
*.piaoquantv.com
*.pingan.com
*.pingwest.com
*.planetmeican.com
*.plex.tv
*.polyfill.io
*.pomotodo.com
*.ppgame.com
*.pplink.link
*.ppsimg.com
*.pptv.com
*.privatehd.to
*.processon.com
*.psbc.com
*.psnine.com
*.pstatp.com
*.pterclub.com
*.pythonclub.org
*.qbox.me
*.qcc.com
*.qcloud.com
*.qcloudcdn.com
*.qcwgg.com
*.qdaily.com
*.qdan.me
*.qdmm.com
*.qeeyou.com
*.qhimg.com
*.qhmsg.com
*.qhres.com
*.qianxin.com
*.qichacha.com
*.qidian.com
*.qihucdn.com
*.qimiaomh.com
*.qingmang.me
*.qingting.fm
*.qiniu.com
*.qiniucdn.com
*.qiniudn.com
*.qiniudns.com
*.qiniup.com
*.qiniuts.com
*.qiuziti.com
*.qiyi.com
*.qiyipic.com
*.qiyukf.com
*.qnssl.com
*.qq.com
*.qqmail.com
*.qqurl.com
*.qqzzz.net
*.quanmingjiexi.com
*.qudong.com
*.qunar.com
*.qweather.com
*.qyer.com
*.qyerstatic.com
*.rapoo.com
*.rarbg.to
*.raychase.net
*.realme.com
*.redacted.ch
*.renren.com
*.renrenche.com
*.renrendoc.com
*.researchgate.net
*.rework.tools
*.rkecloud.com
*.rkidc.net
*.rlcdn.com
*.rom.mk
*.ronghub.com
*.rr.tv
*.rrfmn.com
*.rrimg.com
*.rsc.org
*.ruanmei.com
*.ruanyifeng.com
*.ruby-china.org
*.ruguoapp.com
*.runoob.com
*.s-reader.com
*.sandai.net
*.sankuai.com
*.sarm.net
*.sb.sb
*.sc115.com
*.sciencedirect.com
*.sciencemag.org
*.scofd.com
*.scomper.me
*.sdbeta.com
*.sdo.com
*.seafile.com
*.seele.tech
*.segmentfault.com
*.sekorm.com
*.servicewechat.com
*.sf-express.com
*.shejijia.com
*.shidianguji.com
*.shikezhi.com
*.shimo.im
*.shiyanlou.com
*.shssp.org
*.shxibank.com
*.shyywz.com
*.sigmaaldrich.com
*.sigujiexi.com
*.sina.com
*.sinaapp.com
*.since1989.org
*.siweiearth.com
*.sketchchina.com
*.slack.com
*.sm.ms
*.smart2pay.com
*.smartgslb.com
*.smartisan.com
*.smzdm.com
*.snapdrop.net
*.snssdk.com
*.snwx.com
*.so.com
*.sobot.com
*.sogo.com
*.sogou.com
*.sogoucdn.com
*.sohu-inc.com
*.sohu.com
*.sohucs.com
*.soku.com
*.solidot.org
*.songshuhui.net
*.soso.com
*.soufun.com
*.sourcegcdn.com
*.speedtest.net
*.springer.com
*.springerlink.com
*.springleaf-biomax.com
*.springsunday.net
*.sspai.com
*.stargame.com
*.staticdn.net
*.staticfile.org
*.steamcn.com
*.steamcontent.com
*.subhd.tv
*.sui.com
*.suning.com
*.surface.wiki
*.sznews.com
*.t.tt
*.taichi.graphics
*.taihe.com
*.takungpao.com
*.talkingdata.com
*.tangdou.com
*.tangdouddn.com
*.tanx.com
*.taobao.com
*.taobao.org
*.taobaocdn.com
*.tapdb.net
*.tapimg.com
*.taptap.com
*.tbcache.com
*.tcdn.qq.com
*.tcl.com
*.teambition.com
*.teamviewer.com
*.tencent-cloud.com
*.tencent-cloud.net
*.tencent.com
*.tencentmind.com
*.tengshiauto.com
*.tenpay.com
*.tenxcloud.com
*.test-ipv6.com
*.tgbus.com
*.thefuture.top
*.thomsonreuters.com
*.tianyancha.com
*.tietuku.com
*.tigerlust.com
*.tingyun.com
*.tinyservices.net
*.tinywow.com
*.tjupt.org
*.tmall.com
*.tmall.hk
*.todesk.com
*.tool.lu
*.tophub.today
*.totheglory.im
*.toushibao.com
*.toutiao.com
*.toutiao.io
*.toutiaoimg.com
*.tower.im
*.trontv.com
*.truevue.org
*.ttt.tt
*.tuchong.com
*.tudou.com
*.tuicool.com
*.tuna.moe
*.tuniu.com
*.typeisbeautiful.com
*.u9u9.com
*.ubuntukylin.com
*.ucweb.com
*.ucxinwen.com
*.udache.com
*.udacity.com
*.uedna.com
*.uigreat.com
*.uisdc.com
*.uisheji.com
*.umeng.com
*.umengcloud.com
*.umetrip.com
*.undraw.co
*.uning.com
*.upai.com
*.upaiyun.com
*.upyun.com
*.ustclug.org
*.uuu.moe
*.uxengine.net
*.v-56.com
*.vamaker.com
*.vaptcha.net
*.veryzhun.com
*.vhall.com
*.vhallyun.com
*.videojj.com
*.viosey.com
*.vip.com
*.visualhunt.com
*.visualstudio.com
*.vite.org
*.vjudge.net
*.vmall.com
*.vmware.com
*.voidcn.com
*.vostic.net
*.vpgame.com
*.vpgcdn.com
*.vpsmm.com
*.vss.im
*.vzan.com
*.wacai.com
*.waerfa.com
*.walklake.com
*.wallhaven.cc
*.wandoujia.com
*.wangsu.com
*.wanmei.com
*.weather.com
*.web.guoweishu.net
*.webfont.com
*.webofknowledge.com
*.wechat.com
*.weibo.com
*.weibocdn.com
*.weico.cc
*.weidian.com
*.weidown.com
*.weidunewtab.com
*.weiosx.com
*.weixinbridge.com
*.weiyun.com
*.westlakemuseum.com
*.whatismyip.com
*.wht.im
*.wiley.com
*.windows.com
*.windowsupdate.com
*.wisenjoy.com
*.wjx.top
*.wodemo.com
*.wolai.com
*.woozooo.com
*.woshipm.com
*.woyoo.com
*.wps.com
*.wscdns.com
*.wulihub.com
*.wxb.com
*.xbongbong.com
*.xclient.info
*.xdccpro.com
*.xf9168.com
*.xiachufang.com
*.xiami.com
*.xiami.net
*.xiaoe-tech.com
*.xiaoe-tools.com
*.xiaohongshu.com
*.xiaoka.tv
*.xiaomark.com
*.xiaomi.com
*.xiaomi.net
*.xiaomicp.com
*.xiaomiyoupin.com
*.xiaotu.io
*.xiazaiziti.com
*.ximalaya.com
*.xinhuanet.com
*.xiniu.com
*.xinquji.com
*.xitu.io
*.xiya.vip
*.xldns.net
*.xmac.app
*.xmcdn.com
*.xnpic.com
*.xpcha.com
*.xuanfengge.com
*.xueqiu.com
*.xuetangx.com
*.xujc.com
*.xunlei.com
*.xunyou.com
*.xx1t.com
*.xxsy.net
*.xycdn.com
*.xywy.com
*.yamibo.com
*.yangkeduo.com
*.yangwangauto.com
*.yaohuo.me
*.yd-jxt.com
*.ydstatic.com
*.yecdn.com
*.yesky.com
*.yeyfree.com
*.yfscdn.net
*.yfsvdn.net
*.yhd.com
*.yi2.net
*.yiche.com
*.yihaodianimg.com
*.yinxiang.com
*.yinyuetai.com
*.yizhibo.com
*.ykimg.com
*.ylmf.net
*.youdao.com
*.youku.com
*.youlebe.com
*.youzan.com
*.yunjiasu-cdn.net
*.yunpian.com
*.yunshipei.com
*.yuque.com
*.yuwantech.com
*.yxt.com
*.yy.com
*.z-bank.com
*.zaih.com
*.zanata.org
*.zanmeishi.com
*.zdic.net
*.zealer.com
*.zh.moegirl.org
*.zhan.com
*.zhangxinxu.com
*.zhangyao.name
*.zhangzishi.cc
*.zhanqi.tv
*.zhaopin.com
*.zhihu.com
*.zhihuishu.com
*.zhimap.com
*.zhimg.com
*.zhipin.com
*.zhiye.com
*.zhiziyun.com
*.zhongguose.com
*.zhuihd.com
*.zhujike.com
*.zijieapi.com
*.zimuzu.tv
*.zku.net
*.znyj365.com
*.zto.com
================================================
FILE: packages/gui/extra/scripts/github.script
================================================
// ==UserScript==
// @name Github 增强 - 高速下载
// @name:zh-CN Github 增强 - 高速下载
// @name:zh-TW Github 增強 - 高速下載
// @name:en Github Enhancement - High Speed Download
// @version 2.5.21
// @author X.I.U
// @description 高速下载 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、项目列表单文件快捷下载 (☁)、添加 git clone 命令
// @description:zh-CN 高速下载 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、项目列表单文件快捷下载 (☁)
// @description:zh-TW 高速下載 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、項目列表單文件快捷下載 (☁)
// @description:en High-speed download of Git Clone/SSH, Release, Raw, Code(ZIP) and other files (Based on public welfare), project list file quick download (☁)
// @match *://github.com/*
// @match *://hub.incept.pw/*
// @match *://hub.nuaa.cf/*
// @match *://hub.yzuu.cf/*
// @match *://hub.scholar.rr.nu/*
// @match *://dgithub.xyz/*
// @match *://kkgithub.com/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAACEUExURUxpcRgWFhsYGBgWFhcWFh8WFhoYGBgWFiUlJRcVFRkWFhgVFRgWFhgVFRsWFhgWFigeHhkWFv////////////r6+h4eHv///xcVFfLx8SMhIUNCQpSTk/r6+jY0NCknJ97e3ru7u+fn51BOTsPCwqGgoISDg6empmpoaK2srNDQ0FhXV3eXcCcAAAAXdFJOUwCBIZXMGP70BuRH2Ze/LpIMUunHkpQR34sfygAAAVpJREFUOMt1U+magjAMDAVb5BDU3W25b9T1/d9vaYpQKDs/rF9nSNJkArDA9ezQZ8wPbc8FE6eAiQUsOO1o19JolFibKCdHGHC0IJezOMD5snx/yE+KOYYr42fPSufSZyazqDoseTPw4lGJNOu6LBXVUPBG3lqYAOv/5ZwnNUfUifzBt8gkgfgINmjxOpgqUA147QWNaocLniqq3QsSVbQHNp45N/BAwoYQz9oUJEiE4GMGfoBSMj5gjeWRIMMqleD/CAzUHFqTLyjOA5zjNnwa4UCEZ2YK3khEcBXHjVBtEFeIZ6+NxYbPqWp1DLKV42t6Ujn2ydyiPi9nX0TTNAkVVZ/gozsl6FbrktkwaVvL2TRK0C8Ca7Hck7f5OBT6FFbLATkL2ugV0tm0RLM9fedDvhWstl8Wp9AFDjFX7yOY/lJrv8AkYuz7fuP8dv9izCYH+x3/LBnj9fYPBTpJDNzX+7cAAAAASUVORK5CYII=
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_notification
// @grant window.onurlchange
// @sandbox JavaScript
// @license GPL-3.0 License
// @run-at document-end
// @namespace https://greasyfork.org/scripts/412245
// @supportURL https://github.com/XIU2/UserScript
// @homepageURL https://github.com/XIU2/UserScript
// ==/UserScript==
(function() {
'use strict';
var backColor = '#ffffff', fontColor = '#888888', menu_rawFast = GM_getValue('xiu2_menu_raw_fast'), menu_rawFast_ID, menu_rawDownLink_ID, menu_gitClone_ID, menu_feedBack_ID;
const download_url_us = [
//['https://gh.h233.eu.org/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@X.I.U/XIU2] 提供'],
//['https://gh.api.99988866.xyz/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [hunshcn/gh-proxy] 提供'], // 官方演示站用的人太多了
['https://gh.ddlc.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@mtr-static-official] 提供'],
//['https://gh2.yanqishui.work/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@HongjieCN] 提供'], // 解析错误
['https://dl.ghpig.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [feizhuqwq.com] 提供'],
//['https://gh.flyinbug.top/gh/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [Mintimate] 提供'], // 错误
['https://slink.ltd/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [知了小站] 提供'],
//['https://git.xfj0.cn/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'], // 无解析
['https://gh.con.sh/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'],
//['https://ghps.cc/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'], // 提示 blocked
//['https://gh-proxy.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'], // 502
['https://cors.isteed.cc/github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@Lufs\'s] 提供'],
['https://hub.gitmirror.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [GitMirror] 提供'],
['https://sciproxy.com/github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [sciproxy.com] 提供'],
['https://ghproxy.cc/https://github.com', '美国', '[美国 洛杉矶] - 该公益加速源由 [@yionchiii lau] 提供'],
['https://cf.ghproxy.cc/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@yionchiii lau] 提供'],
['https://gh.jiasu.in/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@0-RTT] 提供'],
['https://dgithub.xyz', '美国', '[美国 西雅图] - 该公益加速源由 [dgithub.xyz] 提供'],
//['https://download.fgit.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 被投诉挂了
['https://download.nuaa.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'],
['https://download.scholar.rr.nu', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'],
//['https://download.njuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 域名挂了
['https://download.yzuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供']
];
const download_url = [
//['https://download.fastgit.org', '德国', '[德国] - 该公益加速源由 [FastGit] 提供
提示:希望大家尽量多使用前面的美国节点(每次随机 4 个来负载均衡),
避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~', 'https://archive.fastgit.org'], // 证书过期
['https://mirror.ghproxy.com/https://github.com', '韩国', '[日本、韩国、德国等](CDN 不固定) - 该公益加速源由 [ghproxy] 提供
提示:希望大家尽量多使用前面的美国节点(每次随机 负载均衡),
避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'],
['https://ghproxy.net/https://github.com', '日本', '[日本 大阪] - 该公益加速源由 [ghproxy] 提供
提示:希望大家尽量多使用前面的美国节点(每次随机 负载均衡),
避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'],
['https://kkgithub.com', '香港', '[中国香港、日本、新加坡等] - 该公益加速源由 [help.kkgithub.com] 提供
提示:希望大家尽量多使用前面的美国节点(每次随机 4 个来负载均衡),
避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'],
//['https://download.incept.pw', '香港', '[中国香港] - 该公益加速源由 [FastGit 群组成员] 提供
提示:希望大家尽量多使用前面的美国节点(每次随机 4 个来负载均衡),
避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'] // ERR_SSL_PROTOCOL_ERROR
];
const clone_url = [
['https://gitclone.com', '国内', '[中国 国内] - 该公益加速源由 [GitClone] 提供
- 缓存:有
- 首次比较慢,缓存后较快'],
['https://kkgithub.com', '香港', '[中国香港、日本、新加坡等] - 该公益加速源由 [help.kkgithub.com] 提供
- 缓存:无(或时间很短)'],
['https://hub.incept.pw', '香港', '[中国香港、美国] - 该公益加速源由 [FastGit 群组成员] 提供'],
['https://mirror.ghproxy.com/https://github.com', '韩国', '[日本、韩国、德国等](CDN 不固定) - 该公益加速源由 [ghproxy] 提供
- 缓存:无(或时间很短)'],
//['https://gh-proxy.com/https://github.com', '韩国', '[韩国] - 该公益加速源由 [ghproxy] 提供
- 缓存:无(或时间很短)'],
['https://githubfast.com', '韩国', '[韩国] - 该公益加速源由 [Github Fast] 提供
- 缓存:无(或时间很短)'],
['https://ghproxy.net/https://github.com', '日本', '[日本 大阪] - 该公益加速源由 [ghproxy] 提供
- 缓存:无(或时间很短)'],
['https://github.moeyy.xyz/https://github.com', '新加坡', '[新加坡、中国香港、日本等](CDN 不固定) - 该公益加速源由 [Moeyy] 提供
- 缓存:无(或时间很短)'],
//['https://slink.ltd/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [知了小站] 提供'] // 暂无必要
//['https://hub.gitmirror.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [GitMirror] 提供'], // 暂无必要
//['https://sciproxy.com/github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [sciproxy.com] 提供'], // 暂无必要
//['https://ghproxy.cc/https://github.com', '美国', '[美国 洛杉矶] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
//['https://cf.ghproxy.cc/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
//['https://gh.jiasu.in/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@0-RTT] 提供'], // 暂无必要
//['https://dgithub.xyz', '美国', '[美国 西雅图] - 该公益加速源由 [dgithub.xyz] 提供'], // 暂无必要
//['https://hub.fgit.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 被投诉挂了
//['https://hub.nuaa.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
//['https://hub.scholar.rr.nu', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
//['https://hub.njuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 域名挂了
//['https://hub.yzuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
//['https://hub.0z.gs', '美国', '[美国 Cloudflare CDN]'], // 域名无解析
//['https://hub.shutcm.cf', '美国', '[美国 Cloudflare CDN]'] // 连接超时
];
const clone_ssh_url = [
['ssh://git@ssh.github.com:443/', 'Github 原生', '[日本、新加坡等] - Github 官方提供的 443 端口的 SSH(依然是 SSH 协议),适用于限制访问 22 端口的网络环境'],
['git@ssh.fastgit.org:', '香港', '[中国 香港] - 该公益加速源由 [FastGit] 提供']
//['git@git.zhlh6.cn:', '美国', '[美国 洛杉矶]'] // 挂了
];
const raw_url = [
['https://raw.githubusercontent.com', 'Github 原生', '[日本 东京]'],
['https://raw.kkgithub.com', '香港', '[中国香港、日本、新加坡等] - 该公益加速源由 [help.kkgithub.com] 提供
- 缓存:无(或时间很短)'],
['https://mirror.ghproxy.com/https://raw.githubusercontent.com', '韩国', '[日本、韩国、德国等](CDN 不固定) - 该公益加速源由 [ghproxy] 提供
- 缓存:无(或时间很短)'],
//['https://gh-proxy.com/https://raw.githubusercontent.com', '韩国 2', '[韩国] - 该公益加速源由 [ghproxy] 提供
- 缓存:无(或时间很短)'],
['https://ghproxy.net/https://raw.githubusercontent.com', '日本 1', '[日本 大阪] - 该公益加速源由 [ghproxy] 提供
- 缓存:无(或时间很短)'],
['https://fastly.jsdelivr.net/gh', '日本 2', '[日本 东京] - 该公益加速源由 [JSDelivr CDN] 提供
- 缓存:有
- 不支持大小超过 50 MB 的文件
- 不支持版本号格式的分支名(如 v1.2.3)'],
['https://fastraw.ixnic.net', '日本 3', '[日本 大阪] - 该公益加速源由 [FastGit 群组成员] 提供
- 缓存:无(或时间很短)'],
//['https://gcore.jsdelivr.net/gh', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [JSDelivr CDN] 提供
- 缓存:有
- 不支持大小超过 50 MB 的文件
- 不支持版本号格式的分支名(如 v1.2.3)'], // 变成 美国 Cloudflare CDN 了
['https://cdn.jsdelivr.us/gh', '其他 1', '[韩国、美国、马来西亚、罗马尼亚等](CDN 不固定) - 该公益加速源由 [@ayao] 提供
- 缓存:有'],
//['https://jsdelivr.b-cdn.net/gh', '其他 2', '[中国香港、台湾、日本、新加坡等](CDN 不固定) - 该公益加速源由 [@rttwyjz] 提供
- 缓存:有'],
['https://github.moeyy.xyz/https://raw.githubusercontent.com', '其他 3', '[新加坡、中国香港、日本等](CDN 不固定)
- 缓存:无(或时间很短)'],
['https://raw.cachefly.998111.xyz', '其他 4', '[新加坡、日本、印度等](Anycast CDN 不固定) - 该公益加速源由 [@XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxX0] 提供
- 缓存:有(约 12 小时)'],
//['https://raw.incept.pw', '香港', '[中国香港、美国] - 该公益加速源由 [FastGit 群组成员] 提供
- 缓存:无(或时间很短)'], // ERR_SSL_PROTOCOL_ERROR
//['https://ghproxy.cc/https://raw.githubusercontent.com', '美国', '[美国 洛杉矶] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
//['https://cf.ghproxy.cc/https://raw.githubusercontent.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
//['https://gh.jiasu.in/https://raw.githubusercontent.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@0-RTT] 提供'], // 暂无必要
//['https://dgithub.xyz', '美国', '[美国 西雅图] - 该公益加速源由 [dgithub.xyz] 提供'], // 暂无必要
//['https://raw.fgit.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供
- 缓存:无(或时间很短)'], // 被投诉挂了
//['https://raw.nuaa.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
//['https://raw.scholar.rr.nu', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
//['https://raw.njuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供
- 缓存:无(或时间很短)'], // 域名挂了
//['https://raw.yzuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供
- 缓存:无(或时间很短)'], // 暂无必要
//['https://raw.gitmirror.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [GitMirror] 提供
- 缓存:有'], // 暂无必要
//['https://cdn.54188.cf/gh', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [PencilNavigator] 提供
- 缓存:有'], // 暂无必要
//['https://raw.fastgit.org', '德国', '[德国] - 该公益加速源由 [FastGit] 提供
- 缓存:无(或时间很短)'], // 挂了
//['https://git.yumenaka.net/https://raw.githubusercontent.com', '美国', '[美国 圣何塞]
- 缓存:无(或时间很短)'], // 连接超时
];
const svg = [
''
], style = ['padding:0 6px; margin-right: -1px; border-radius: 2px; background-color: var(--XIU2-back-Color); border-color: rgba(27, 31, 35, 0.1); font-size: 11px; color: var(--XIU2-font-Color);'];
if (menu_rawFast == null){menu_rawFast = 1; GM_setValue('xiu2_menu_raw_fast', 1)};
if (GM_getValue('menu_rawDownLink') == null){GM_setValue('menu_rawDownLink', true)};
if (GM_getValue('menu_gitClone') == null){GM_setValue('menu_gitClone', true)};
registerMenuCommand();
// 注册脚本菜单
function registerMenuCommand() {
// 如果反馈菜单ID不是 null,则删除所有脚本菜单
if (menu_feedBack_ID) {GM_unregisterMenuCommand(menu_rawFast_ID); GM_unregisterMenuCommand(menu_rawDownLink_ID); GM_unregisterMenuCommand(menu_gitClone_ID); GM_unregisterMenuCommand(menu_feedBack_ID); menu_rawFast = GM_getValue('xiu2_menu_raw_fast');}
// 避免在减少 raw 数组后,用户储存的数据大于数组而报错
if (menu_rawFast > raw_url.length - 1) menu_rawFast = 0
menu_rawDownLink_ID = GM_registerMenuCommand(`${GM_getValue('menu_rawDownLink')?'✅':'❌'} 项目列表单文件快捷下载 (☁)`, function(){if (GM_getValue('menu_rawDownLink') == true) {GM_setValue('menu_rawDownLink', false); GM_notification({text: `已关闭「项目列表单文件快捷下载 (☁)」功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});} else {GM_setValue('menu_rawDownLink', true); GM_notification({text: `已开启「项目列表单文件快捷下载 (☁)」功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});}registerMenuCommand();}, {title: "点击开关「项目列表单文件快捷下载 (☁)」功能"});
if (GM_getValue('menu_rawDownLink')) menu_rawFast_ID = GM_registerMenuCommand(` ${['0️⃣','1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟'][menu_rawFast]} [ ${raw_url[menu_rawFast][1]} ] 加速源 (☁) - 点击切换`, menu_toggle_raw_fast, {title: "点击切换「项目列表单文件快捷下载 (☁)」功能的加速源"});
menu_gitClone_ID = GM_registerMenuCommand(`${GM_getValue('menu_gitClone')?'✅':'❌'} 添加 git clone 命令`, function(){if (GM_getValue('menu_gitClone') == true) {GM_setValue('menu_gitClone', false); GM_notification({text: `已关闭「添加 git clone 命令」功能`, timeout: 3500});} else {GM_setValue('menu_gitClone', true); GM_notification({text: `已开启「添加 git clone 命令」功能`, timeout: 3500});}registerMenuCommand();}, {title: "点击开关「添加 git clone 命令」功能"});
menu_feedBack_ID = GM_registerMenuCommand('💬 反馈问题 & 功能建议', function () {GM_openInTab('https://github.com/XIU2/UserScript', {active: true,insert: true,setParent: true});GM_openInTab('https://greasyfork.org/zh-CN/scripts/412245/feedback', {active: true,insert: true,setParent: true});}, {title: "点击前往反馈问题或提出建议"});
}
// 切换加速源
function menu_toggle_raw_fast() {
// 如果当前加速源位置大于等于加速源总数,则改为第一个加速源,反之递增下一个加速源
if (menu_rawFast >= raw_url.length - 1) {menu_rawFast = 0;} else {menu_rawFast += 1;}
GM_setValue('xiu2_menu_raw_fast', menu_rawFast);
delRawDownLink(); // 删除旧加速源
addRawDownLink(); // 添加新加速源
GM_notification({text: "已切换加速源为:" + raw_url[menu_rawFast][1], timeout: 3000}); // 提示消息
registerMenuCommand(); // 重新注册脚本菜单
};
colorMode(); // 适配白天/夜间主题模式
setTimeout(addRawFile, 1000); // Raw 加速
setTimeout(addRawDownLink, 2000); // Raw 单文件快捷下载(☁),延迟 2 秒执行,避免被 pjax 刷掉
// Tampermonkey v4.11 版本添加的 onurlchange 事件 grant,可以监控 pjax 等网页的 URL 变化
if (window.onurlchange === undefined) addUrlChangeEvent();
window.addEventListener('urlchange', function() {
colorMode(); // 适配白天/夜间主题模式
if (location.pathname.indexOf('/releases')) addRelease(); // Release 加速
setTimeout(addRawFile, 1000); // Raw 加速
setTimeout(addRawDownLink, 2000); // Raw 单文件快捷下载(☁),延迟 2 秒执行,避免被 pjax 刷掉
setTimeout(addRawDownLink_, 1000); // 在浏览器返回/前进时重新添加 Raw 下载链接(☁)鼠标事件
});
// Github Git Clone/SSH、Release、Download ZIP 改版为动态加载文件列表,因此需要监控网页元素变化
const callback = (mutationsList, observer) => {
if (location.pathname.indexOf('/releases') > -1) { // Release
for (const mutation of mutationsList) {
for (const target of mutation.addedNodes) {
if (target.nodeType !== 1) return
if (target.tagName === 'DIV' && target.dataset.viewComponent === 'true' && target.classList[0] === 'Box') addRelease();
}
}
} else if (document.querySelector('#repository-container-header:not([hidden])')) { // 项目首页
for (const mutation of mutationsList) {
for (const target of mutation.addedNodes) {
if (target.nodeType !== 1) return
if (target.tagName === 'DIV' && target.parentElement.id === '__primerPortalRoot__') {
addDownloadZIP(target);
if (addGitClone(target) === false) return;
if (addGitCloneSSH(target) === false) return;
} else if (target.tagName === 'DIV' && target.className.indexOf('Box-sc-') !== -1) {
if (target.querySelector('input[value^="https:"]')) {
addGitCloneClear('.XIU2-GCS');
if (addGitClone(target) === false) return;
} else if (target.querySelector('input[value^="git@"]')) {
addGitCloneClear('.XIU2-GC');
if (addGitCloneSSH(target) === false) return;
} else if (target.querySelector('input[value^="gh "]')) {
addGitCloneClear('.XIU2-GC, .XIU2-GCS');
}
}
}
}
}
};
const observer = new MutationObserver(callback);
observer.observe(document, { childList: true, subtree: true });
// download_url 随机 4 个美国加速源
function get_New_download_url() {
//return download_url_us.concat(download_url) // 全输出调试用
let shuffled = download_url_us.slice(0), i = download_url_us.length, min = i - 4, temp, index;
while (i-- > min) {index = Math.floor((i + 1) * Math.random()); temp = shuffled[index]; shuffled[index] = shuffled[i]; shuffled[i] = temp;}
return shuffled.slice(min).concat(download_url); // 随机洗牌 download_url_us 数组并取前 4 个,然后将其合并至 download_url 数组
}
// Release
function addRelease() {
let html = document.querySelectorAll('.Box-footer'); if (html.length == 0 || location.pathname.indexOf('/releases') == -1) return
let divDisplay = 'margin-left: -90px;', new_download_url = get_New_download_url();
if (document.documentElement.clientWidth > 755) {divDisplay = 'margin-top: -3px;margin-left: 8px;display: inherit;';}; // 调整小屏幕时的样式
for (const current of html) {
if (current.querySelector('.XIU2-RS')) continue
current.querySelectorAll('li.Box-row a').forEach(function (_this) {
let href = _this.href.split(location.host),
url = '', _html = ``;
for (let i=0;i${new_download_url[i][1]}`
}
_this.parentElement.nextElementSibling.insertAdjacentHTML('beforeend', _html + '
');
});
}
}
// Download ZIP
function addDownloadZIP(target) {
let html = target.querySelector('ul[class^=List__ListBox-sc-] ul[class^=List__ListBox-sc-]>li:last-child');if (!html) return
let href_script = document.querySelector('react-partial[partial-name=repos-overview]>script[data-target="react-partial.embeddedData"]'),
href_slice = href_script.textContent.slice(href_script.textContent.indexOf('"zipballUrl":"')+14),
href = href_slice.slice(0, href_slice.indexOf('"')),
url = '', _html = '', new_download_url = get_New_download_url();
// 克隆原 Download ZIP 元素,并定位 标签
let html_clone = html.cloneNode(true),
html_clone_a = html_clone.querySelector('a[href$=".zip"]'),
html_clone_span = html_clone.querySelector('span[id]');
for (let i=0;i{e.remove()})
}
// Git Clone
function addGitClone(target) {
let html = target.querySelector('input[value^="https:"]');if (!html) return
if (!html.nextElementSibling) return false;
let href_split = html.value.split(location.host)[1],
html_parent = '',
url = '', _html = '', _gitClone = '';
html.nextElementSibling.hidden = true; // 隐藏右侧复制按钮(考虑到能直接点击复制,就不再重复实现复制按钮事件了)
if (GM_getValue('menu_gitClone')) {_gitClone='git clone '; html.value = _gitClone + html.value; html.setAttribute('value', html.value);}
// 克隆原 Git Clone 元素
let html_clone = html.cloneNode(true);
for (let i=0;i
'
}
html.parentElement.insertAdjacentHTML('afterend', _html);
}
// Git Clone SSH
function addGitCloneSSH(target) {
let html = target.querySelector('input[value^="git@"]');if (!html) return
if (!html.nextElementSibling) return false;
let href_split = html.value.split(':')[1],
html_parent = '',
url = '', _html = '', _gitClone = '';
html.nextElementSibling.hidden = true; // 隐藏右侧复制按钮(考虑到能直接点击复制,就不再重复实现复制按钮事件了)
if (GM_getValue('menu_gitClone')) {_gitClone='git clone '; html.value = _gitClone + html.value; html.setAttribute('value', html.value);}
// 克隆原 Git Clone SSH 元素
let html_clone = html.cloneNode(true);
for (let i=0;i
'
}
html.parentElement.insertAdjacentHTML('afterend', _html);
}
// Raw
function addRawFile() {
let html = document.querySelector('a[data-testid="raw-button"]');if (!html) return
let href = location.href.replace(`https://${location.host}`,''),
href2 = href.replace('/blob/','/'),
url = '', _html = '';
for (let i=1;i${raw_url[i][1].replace(/ \d/,'')}`
}
if (document.querySelector('.XIU2-RF')) document.querySelectorAll('.XIU2-RF').forEach((e)=>{e.remove()})
html.insertAdjacentHTML('afterend', _html);
}
// Raw 单文件快捷下载(☁)
function addRawDownLink() {
if (!GM_getValue('menu_rawDownLink')) return
// 如果不是项目文件页面,就返回,如果网页有 Raw 下载链接(☁)就返回
let files = document.querySelectorAll('div.Box-row svg.octicon.octicon-file, .react-directory-filename-column>svg.color-fg-muted');if(files.length === 0) return;if (location.pathname.indexOf('/tags') > -1) return
let files1 = document.querySelectorAll('a.fileDownLink');if(files1.length > 0) return;
// 鼠标指向则显示
var mouseOverHandler = function(evt) {
let elem = evt.currentTarget,
aElm_new = elem.querySelectorAll('.fileDownLink'),
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
aElm_new.forEach(el=>{el.style.cssText = 'display: inline'});
aElm_now.forEach(el=>{el.style.cssText = 'display: none'});
};
// 鼠标离开则隐藏
var mouseOutHandler = function(evt) {
let elem = evt.currentTarget,
aElm_new = elem.querySelectorAll('.fileDownLink'),
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
aElm_new.forEach(el=>{el.style.cssText = 'display: none'});
aElm_now.forEach(el=>{el.style.cssText = 'display: inline'});
};
// 循环添加
files.forEach(function(fileElm) {
let trElm = fileElm.parentNode.parentNode,
cntElm_a = trElm.querySelector('[role="rowheader"] > .css-truncate.css-truncate-target.d-block.width-fit > a, .react-directory-truncate>a'),
Name = cntElm_a.innerText,
href = cntElm_a.getAttribute('href'),
href2 = href.replace('/blob/','/'), url = '';
if ((raw_url[menu_rawFast][0].indexOf('/gh') + 3 === raw_url[menu_rawFast][0].length) && raw_url[menu_rawFast][0].indexOf('cdn.staticaly.com') === -1) {
url = raw_url[menu_rawFast][0] + href.replace('/blob/','@');
} else {
url = raw_url[menu_rawFast][0] + href2;
}
fileElm.insertAdjacentHTML('afterend', `${svg[0]}`);
// 绑定鼠标事件
trElm.onmouseover = mouseOverHandler;
trElm.onmouseout = mouseOutHandler;
});
}
// 移除 Raw 单文件快捷下载(☁)
function delRawDownLink() {
if (!GM_getValue('menu_rawDownLink')) return
let aElm = document.querySelectorAll('.fileDownLink');if(aElm.length === 0) return;
aElm.forEach(function(fileElm) {fileElm.remove();})
}
// 在浏览器返回/前进时重新添加 Raw 单文件快捷下载(☁)鼠标事件
function addRawDownLink_() {
if (!GM_getValue('menu_rawDownLink')) return
// 如果不是项目文件页面,就返回,如果网页没有 Raw 下载链接(☁)就返回
let files = document.querySelectorAll('div.Box-row svg.octicon.octicon-file, .react-directory-filename-column>svg.color-fg-muted');if(files.length === 0) return;
let files1 = document.querySelectorAll('a.fileDownLink');if(files1.length === 0) return;
// 鼠标指向则显示
var mouseOverHandler = function(evt) {
let elem = evt.currentTarget,
aElm_new = elem.querySelectorAll('.fileDownLink'),
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
aElm_new.forEach(el=>{el.style.cssText = 'display: inline'});
aElm_now.forEach(el=>{el.style.cssText = 'display: none'});
};
// 鼠标离开则隐藏
var mouseOutHandler = function(evt) {
let elem = evt.currentTarget,
aElm_new = elem.querySelectorAll('.fileDownLink'),
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
aElm_new.forEach(el=>{el.style.cssText = 'display: none'});
aElm_now.forEach(el=>{el.style.cssText = 'display: inline'});
};
// 循环添加
files.forEach(function(fileElm) {
let trElm = fileElm.parentNode.parentNode;
// 绑定鼠标事件
trElm.onmouseover = mouseOverHandler;
trElm.onmouseout = mouseOutHandler;
});
}
// 适配白天/夜间主题模式
function colorMode() {
let style_Add;
if (document.getElementById('XIU2-Github')) {style_Add = document.getElementById('XIU2-Github')} else {style_Add = document.createElement('style'); style_Add.id = 'XIU2-Github'; style_Add.type = 'text/css';}
backColor = '#ffffff'; fontColor = '#888888';
if (document.lastElementChild.dataset.colorMode === 'dark') { // 如果是夜间模式
if (document.lastElementChild.dataset.darkTheme === 'dark_dimmed') {
backColor = '#272e37'; fontColor = '#768390';
} else {
backColor = '#161a21'; fontColor = '#97a0aa';
}
} else if (document.lastElementChild.dataset.colorMode === 'auto') { // 如果是自动模式
if (window.matchMedia('(prefers-color-scheme: dark)').matches || document.lastElementChild.dataset.lightTheme.indexOf('dark') > -1) { // 如果浏览器是夜间模式 或 白天模式是 dark 的情况
if (document.lastElementChild.dataset.darkTheme === 'dark_dimmed') {
backColor = '#272e37'; fontColor = '#768390';
} else if (document.lastElementChild.dataset.darkTheme.indexOf('light') == -1) { // 排除夜间模式是 light 的情况
backColor = '#161a21'; fontColor = '#97a0aa';
}
}
}
document.lastElementChild.appendChild(style_Add).textContent = `.XIU2-RS a {--XIU2-back-Color: ${backColor}; --XIU2-font-Color: ${fontColor};}`;
}
// 自定义 urlchange 事件(用来监听 URL 变化),针对非 Tampermonkey 油猴管理器
function addUrlChangeEvent() {
history.pushState = ( f => function pushState(){
var ret = f.apply(this, arguments);
window.dispatchEvent(new Event('pushstate'));
window.dispatchEvent(new Event('urlchange'));
return ret;
})(history.pushState);
history.replaceState = ( f => function replaceState(){
var ret = f.apply(this, arguments);
window.dispatchEvent(new Event('replacestate'));
window.dispatchEvent(new Event('urlchange'));
return ret;
})(history.replaceState);
window.addEventListener('popstate',()=>{ // 点击浏览器的前进/后退按钮时触发 urlchange 事件
window.dispatchEvent(new Event('urlchange'))
});
}
})();
================================================
FILE: packages/gui/extra/scripts/google.js
================================================
// ==UserScript==
// @name google增强
// @version 1.2.4
// @author Greper
// @description 去除ping链接
// @match https://www.google.com/*/*
// @icon https://www.google.com/favicon.ico
// @license GPL-3.0 License
// @run-at document-end
// @namespace
// ==/UserScript==
(function () {
console.log('google script loaded')
const aList = document.getElementsByTagName('a')
for (let i = 0; i <= aList.length; i++) {
console.log(aList[i].href)
aList[i].ping = undefined
}
})()
================================================
FILE: packages/gui/extra/scripts/tampermonkey.script
================================================
/**
* 篡改猴(Tampermonkey)| 油猴(Greasemonkey)浏览器脚本扩展
*
* @version 0.1.4
* @since 2024-04-24 17:06
* @author 王良
* @authorHomePage https://wangliang1024.cn
* @remark 当前脚本为仿照的版本,并非篡改猴插件的源码,仅供学习参考。
* @description 篡改猴 (Tampermonkey) 是拥有 超过 1000 万用户 的最流行的浏览器扩展之一。 它适用于 Chrome、Microsoft Edge、Safari、Opera Next 和 Firefox。
* 有些人也会把篡改猴(Tampermonkey)称作油猴(Greasemonkey),尽管后者只是一款仅适用于 Firefox 浏览器的浏览器扩展程序。
* 它允许用户自定义并增强您最喜爱的网页的功能。用户脚本是小型 JavaScript 程序,可用于向网页添加新功能或修改现有功能。使用 篡改猴,您可以轻松在任何网站上创建、管理和运行这些用户脚本。
* 例如,使用 篡改猴,您可以向网页添加一个新按钮,可以快速在社交媒体上分享链接,或自动填写带有个人信息的表格。在数字化时代,这特别有用,因为网页常常被用作访问广泛的服务和应用程序的用户界面。
* 此外,篡改猴 使您轻松找到并安装其他用户创建的用户脚本。这意味着您可以快速轻松地访问为您喜爱的网页定制的广泛库,而无需花费数小时编写自己的代码。
* 无论您是希望为您的站点添加新功能的 Web 开发人员,还是只是希望 改善在线体验的普通用户,篡改猴 都是您的工具箱中的一个很好的工具。
* @homepageUrl https://www.tampermonkey.net
*/
'use strict';
(function () {
const version = "0.1.4";
const PRE = "DS-Tampermonkey:"; // 前缀
const MENU_ID_PRE = PRE + "menu-";
const icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwEAQAAACtm+1PAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAACqjSMyAAAACXBIWXMAAABIAAAASABGyWs+AAAL+UlEQVRo3tWaeXRUdZbHP7+qkI1ABIQJryoJEAkYaAJECBEEGhAIgiw2DAfBAUEE9MjixtjSKNotyICI4CggckCddsSWRZYASqvIIltQo0ADQdaEfctGSH3nj9/L0ggIkjPY95yqd+q9V/fe77339373e6vgX1zMtS4+2CRuzo+fDOl4IbfDY4H2DR7P3xjWMXAS8AKhV/lSDhC4htIwIOgq1y6BJwJCU/NWeb7PmBEem9a0wZ/fjX7/ob1DbgjA042jeq8YNtGcm/vQhqIkc8C8BYwBugHRYO4AfK6ztwHhZbRdAgqAY0Cwe+0MUAmo7DpfbFXAUaACEAH6EfgBWAlMBj0N3n2Kifx8fkqXPmM16ZWsj34RQPfHW7b7Ln5h14L0qNEmF8wa4EMww4BBwO/cVxVXQSzQyDoAwCmgEDjkRjsCOO7eH+m+ijNwAtgCOu1+5wdgB5AGehF4AtQKFA4hfbNeq/dpj54rntxU+6oAUucnZ+4cvDb24udhxlQHkwPmWzCfAqPAdASSgKZALOAB8lynzpeC+kU542aj2PolIBvYCnwPWgn8F2ggqDaoks1O8IA8JdT+/U/LVpeCKAHw7JdRvT8K2Z5SEIga7ekGxg8m2I3+h8AIMN2Be4B/c6NYFbsWKrnnKlwnAICTbpkVuZkoAHJtRvgYNBt4DNQcFAH6BgKfQGi/rNf63NVkwysf23IqWU4r908eUrA9qqMZCaYumBgwZ8HE2WhwAsxPQJQLOxg4dwMOX0sCQD6QBewHXQQzF7t+agLVLDhTE/I7Ro1e8fHkBjDgI9wiYGjleJ1e8OAQcxxMHpiKYM6DOQRkgFkF5mNguFs+ES6A8hKP62wd4A9gpoBZCXwDZjeYLDBeMGH2eGr4g0Me/Uu8SgBsWT7oYFFLc8BsAbMPWARmLTAPzNtA+3J09nolGcwsYCmY1e4r3fpVVGQObJk/6CC4a6Dec5u75cy+a4lZAp7XwbS09W+WAm1ugfNlZTOoLSgYtAECQ0HdoOKMLffv6tNsqQGIy8tNK5gc1tHzdzCvgycROOlG33uLAQBMAIVAYB3oKQi0hNBZeav2ZIZ3MgD+YxLHwPMteJ4Dc7d9fPIft9pzV74EJYDWQOAJCNwBpMAhjzFBAIoFTyGwDlvvtbl6q3ArpKnrU8D1LwwCOfaSB7CbUR0wrYB6QDTXvyn9f0gEUB+IAxMPph4lpe0peb8d+yirjI2+51Z7fZnEAzWAi4DARJQFgOt8ODYbxah/S1LJ9S/BftSpywEUS/GZ87fa4yv4FcBmAC4rIYALoMP2SB621f0tyUlK2w3ARNpjKbU4DJwF9mF7nJ3Yvue3IAHXn8PYDJTZmyyAO7HE4htsNxgNrAXa3mrPXdmI7XQPWu4gbMNHpeISKgDtA/5he3H9D2gBMBnYe4udzwTNxwb0K2ADtpz22Mt2I9sBZiOWynmwZfQB6Elsds5cQXEFoDWYRKAZkIJlYNcjF4E1rnM7sFk/XOZ6dWxnGopldqNAfwJ5gRXYdXpbGQAUArtBH9iNQt+6IOq6yi64qLdi6aEHSyODQJlgHgFqgRkKPIjdDK/EtncCfwO95epfC9qELZFdWHJTD0twggE/lgt0Bx4GkoFlwBNAw7IAjgGrgSO2WTLtQR2xpP1zYCkozY3KfjeC9cH0AMbacjN/diMWBQwC0xlIxNLFjaB04A17jw5jWdd/A31AS7HszmApa0MwDwG9XBAAna1P6gtMB2LKAtgBLALWgb52P2cBM0EfAgdsO8s2Sp8GB+zRTLftt9bZVsQsB1MdNARogt18QkFBbsksBDUCloDO2PLUBrfGC90sNLCVQD83q52xu3APoLGbsbvLAvjJdfwkmA6ge62zmgC0A02C0FZH61Wo/NXSwN93j/TGFY7iKadn0YKmEfmfNn05sMc71FRzo5hnCbh5Abt7XrJAtQuYB/orqIJ9kngWFM0Kq7p1Fp02nQ85e2J6Ye8K09QwrlGR03ZR7taau0wyaB6YUW7J3g0aCyoAllsABsAZI5k8MIuAVECgiUBvqBy57U/+/uOc9/ulBWoUFT12eVmPuD1mzNf/O2r5mbTH+xbtqzDe+MB4wKQBA9yIvgNqbw3rMHiHFr5YpcOMvya3nXL07e8On71cp+Sd2WF423sOtX7pyIVZKZ1YCmacLVvmgpJAOXBkvrErzRkk+RpKvvck30rJN1CKbqxJLd4av/zEXu94rkN6fpGYUefA7lzfD1J0uBS9V4rpKsXcL0VvlKKN5NshxUXuzu31XWLG9eg8X+gdn7Lw+Qf8ay4d9A2UfFsl3weSL1Zyequ0V3CSJOeY5Ksl+YIl3zqldNj+ZNL1GCkrwx5w+t8xdl9f3zLJ/5EUXUuKbij5P5B8f5PqpO/OfXSr0/9G9baIGzTJH6sUXxXJd5fkHJactmUBJEjOF5KzR3JGS4mb36l9o0ZKMtE/MSPWc/EF3wzJ30DyJ0u+uVKs5+ILve67vshfSRrXebO/80fJ2S05cyXnzrIAkiWns+R0k2qHnh4xNify+K81BJD41NT6zhbJ11/yDZOcdVKjFVPr34zOVzZEHq8z8vQI51HJSZWcOJWOVbiIXc5VIHz/7MMTK56tfjPGfv/AtC7e3UWzNBQ0DLyzi2a1/n5al5vR+Z8pZ6tXSp59mJPuiaNlLjqhkvO25CRKqUEtsm7GULHET9iU5HSVnJ5S3ZWbbng9XUl6V26R5bSSnOGSE1w2AwHgAgS9pzbvPp0+rTyMEbvpPEHYjnHxlj7loXL+7vRpwTvUhndKz5USmskQ1klNo17Jn1gexkKmnJhOEWDAk52dWB46w6LyJyr1RGTZc/9EKQMdysOMlcIHKkyjMhAJ6h9abgQ1aHrl6T8D4HGZV+5mT9WZg/2Ly8OQmR3/OhWwVPVS3B/KQ+fsbf7F+eNCFgB4WpYBENIobxW1QQWMe29O6xY3ayinuXfmxUP3dOMiEASXftd2yDGvd+bN6p2T0GqlljGOJhDSLm9VCYCg7IwZ+IDmcKb5oEkSvW7GUI9xnTx5y2ruYiQwBPK+rDHnoS+6PHIzOiV6nev8cD2aAfEQdCJjRgmAME9aU7YDSXA+tMOU7kXtdv5aQydjvOMPLnrpCCFg+oJ5GIiDzLUvvHT8OvuqK8n9Ge12nj9x70haAeshOHV5egmAxOR3o70TFUMeBBpBxt45jQe/GPnZrzGUmvF88rmkphPM8zb6DLat9fk6TSd0cZ5P/jU6H5kZ+dkP++c0DrQHvOD9UDF3jZj/z8FI3LUgzWnhbmjtpfht6yOe2Vot9kYMtdv3ZJL/a03yNZB8CyV/TclfR/KtlnxNJH+6JrX77MaaxGc2V4uNL1wf4YRJzhuS01JquG7u9J/dOOLO28/VSTs61WkjOW9KTjMpbsS+fve93S7hl9bEmOzI402i5r3sm64UX5jke1Xy15D8IZK/tuT3u616fck3RymNd817ecxz1+63JHp1ndEuIW7kvn5OqOSMcX0yR6c+nF7tyk+1rtuSM2vNzQ04vSRnquQMsRFM6PB5RkpSv+w3sksfsdnvhY7tYlpkNYh5tXuttadHOMNsm+vrK/nSJf89kj9W8reWfEWSL1/yDZKcfMl5Vqq1+PSIhJ9e7d45p0XWkezQscV6Z67wL05Z3i+7/oDVY/yxkjNScmbayNf6KjfQLT85s6zPP5sddItKzszYtOiT/HujRtMP+yvhbCAfzB5eCvcFThX9JXfnpXMVcy+9ab6gCpbI/xHwgXkOO1kYAOZ2SkaUeh9LUydiJ3/PAJnWg6C2ahN0R064d0Z4/dwfPVVViXHchx3XFADzIOzHrNca1O3Rc8nma/zQXSyj766xYvWrE/9xNn/g5KKB5gDDgRDsiOVyWYqljanYHyISwDRyQQTc7xViJx/bXG78FZCOHQ50u0xfDnawnAmsB+9Cxdy2aN7THbuPrTul3rHUy81f888egyvFzdn67KCDuUvaP1aUkrg9f2RYRzW77KYooKJr2IudQlTFznN87j2nsO3vaezgOBc7vj/Fz4ZmZj2Ezs1b5T20o0nIv69Kar5m/vh3pl/9zx7/8vJ/39rPvCQFiBIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMDEtMjlUMjM6NDM6MjcrMDE6MDDuWSV6AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTAxLTI5VDIzOjQzOjI3KzAxOjAwnwSdxgAAAFl0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9qYW5iL1Byb2pla3RlL3RhbXBlcm1vbmtleS9yZWwvaW1hZ2VzL2luY2x1ZGVzL3RhbXBlcm1vbmtleS5zdmf3en/XAAAAAElFTkSuQmCC";
const context = {
initialized: false, // 是否已经初始化
defaultPluginOptions: {}, // 默认插件选项
pluginOptions: {}, // 插件选项
styleElement: null, // 插件样式元素
pluginElement: null, // 插件元素
arrowElement: null, // 箭头元素
menusElement: null, // 菜单列表元素
userMenusElement: null, // 用户菜单列表元素
menus: {}, // 菜单集合
menuIndex: 0, // 菜单索引,用于生成menuCmdId
lastNotification: null // 最后一次通知
/* 最后一次通知的对象结构如下:
{
obj: null, // 通知对象,类型:Notification
options: null, // 通知选项
timeout: null // 通知定时器
}
*/
};
// 创建插件API
const api = {};
// 监听页面关闭事件,用于关闭最后一个通知
window.addEventListener('beforeunload', function(event) {
api.closeLastNotification();
});
//region DS自定义的API start
// 获取上下文
api.getContext = () => context;
// 创建插件样式
api.createPluginStyle = (options) => {
options = options || {};
// 创建一个新的
================================================
FILE: packages/gui/src/view/api.js
================================================
import { ipcRenderer, shell } from 'electron'
import lodash from 'lodash'
import path from 'node:path'
let inited = false
let apiObj = null
export function apiInit (app) {
const invoke = (api, args) => {
return ipcRenderer.invoke('apiInvoke', [api, args]).catch((e) => {
app.$notification.error({
message: 'Api invoke error',
description: e.message,
})
})
}
const send = (channel, message) => {
console.log('ipcRenderer.send, channel=', channel, ', message=', message)
return ipcRenderer.send(channel, message)
}
apiObj = {
ipc: {
on (channel, callback) {
ipcRenderer.on(channel, callback)
},
removeAllListeners (channel) {
ipcRenderer.removeAllListeners(channel)
},
invoke,
postMessage (channel, ...args) {
ipcRenderer.postMessage(channel, ...args)
},
send,
async openExternal (href) {
await shell.openExternal(href)
},
openPath (file) {
shell.openPath(path.resolve(file))
},
},
}
const bindApi = (api, param1) => {
lodash.set(apiObj, api, (param2) => {
return invoke(api, param2 || param1)
})
}
if (!inited) {
return invoke('getApiList').then((list) => {
inited = true
for (const item of list) {
bindApi(item)
}
console.log('api inited:', apiObj)
return apiObj
})
}
return new Promise((resolve) => {
resolve(apiObj)
})
}
export function useApi () {
return apiObj
}
================================================
FILE: packages/gui/src/view/components/container.vue
================================================
================================================
FILE: packages/gui/src/view/components/mock-input.vue
================================================
================================================
FILE: packages/gui/src/view/components/setup-ca.vue
================================================
{{ title }}
点此去安装
为什么要安装证书?
本应用在非“安全模式”下必须安装和信任CA根证书,该证书是应用启动时本地随机生成的
1、点击右上角“点此去安装按钮”,打开钥匙串,选择”系统“
2、然后按如下图步骤将随机生成的根证书设置为始终信任
3、可能需要重新启动应用和浏览器才能生效
4、注意:如果出现无法导入提示时,先点一下钥匙串的左边切换到“系统”栏,然后再重新安装证书即可
1、点击右上角“点此去安装按钮”,将自动安装到系统证书库中
2、火狐、chrome等浏览器不走系统证书,需要手动安装(下图以chrome为例安装根证书)
1、点击右上角“点此去安装按钮”,打开证书
2、然后按如下图步骤将根证书添加到信任的根证书颁发机构
================================================
FILE: packages/gui/src/view/components/tree-node.vue
================================================
================================================
FILE: packages/gui/src/view/composables/theme.js
================================================
import { ref } from 'vue'
export const colorTheme = ref('dark')
================================================
FILE: packages/gui/src/view/index.js
================================================
import modules from '../bridge/front'
import { apiInit, useApi } from './api'
import status from './status'
export default {
initApi: apiInit,
async initPre (Vue, api) {
Vue.prototype.$api = api
const setting = await api.setting.load()
Vue.prototype.$global = {
setting,
config: await api.config.get(),
}
await status.install(api)
},
initModules (app, router) {
const api = useApi()
modules.install(app, api, router)
},
}
================================================
FILE: packages/gui/src/view/mixins/plugin.js
================================================
import lodash from 'lodash'
import DsContainer from '../components/container'
export default {
components: {
DsContainer,
},
data () {
return {
key: undefined,
config: undefined,
status: {},
labelCol: { span: 5 },
wrapperCol: { span: 19 },
resetDefaultLoading: false,
applyLoading: false,
systemPlatform: '',
}
},
created () {
this.init()
},
mounted () {
},
methods: {
getKey () {
if (this.key) {
return this.key
}
throw new Error('请设置key')
},
async init () {
this.status = this.$status
await this.reloadConfig()
this.printConfig('Init, ')
this.systemPlatform = await this.$api.info.getSystemPlatform()
if (this.ready) {
return this.ready(this.config)
}
},
async apply () {
if (this.applyLoading === true) {
return // 防重复提交
}
this.applyLoading = true
try {
await this.applyBefore()
await this.saveConfig()
await this.applyAfter()
} finally {
this.applyLoading = false
}
},
async applyBefore () {
},
async applyAfter () {
},
resetDefault () {
const key = this.getKey()
this.$confirm({
title: '提示',
content: '确定要恢复默认设置吗?',
cancelText: '取消',
okText: '确定',
onOk: async () => {
this.resetDefaultLoading = true
try {
this.config = await this.$api.config.resetDefault(key)
if (this.ready) {
await this.ready(this.config)
}
await this.apply()
} finally {
this.resetDefaultLoading = false
}
},
onCancel () {},
})
},
saveConfig () {
return this.$api.config.save(this.config).then((ret) => {
this.$message.success('设置已保存')
this.setConfig(ret.allConfig)
this.printConfig('After saveConfig(), ')
return ret
})
},
getConfig (key) {
const value = lodash.get(this.config, key)
if (value == null) {
return {}
}
return value
},
setConfig (newConfig) {
this.$set(this, 'config', newConfig)
},
printConfig (prefix = '') {
console.log(`${prefix}${this.key} page config:`, this.config, this.systemPlatform)
},
getStatus (key) {
const value = lodash.get(this.status, key)
if (value == null) {
return {}
}
return value
},
async reloadConfig () {
const config = await this.$api.config.reload()
this.setConfig(config)
},
async reloadConfigAndRestart () {
if (this.$api.plugin.git.isEnabled()) {
await this.$api.plugin.git.close()
}
await this.reloadConfig()
this.printConfig('After reloadConfigAndRestart(), ')
if (this.status.server.enabled || this.status.proxy.enabled) {
await this.$api.proxy.restart()
await this.$api.server.restart()
if (this.$api.plugin.git.isEnabled()) {
await this.$api.plugin.git.start()
}
this.$message.success('代理服务和系统代理重启成功')
} else {
this.$message.info('代理服务和系统代理未启动,无需重启')
}
},
isWindows () {
return this.systemPlatform === 'windows'
},
isMac () {
return this.systemPlatform === 'mac'
},
isLinux () {
return this.systemPlatform === 'linux'
},
async openLog () {
const dir = await this.$api.info.getLogDir()
this.$api.ipc.openPath(dir)
},
async focusFirst (ref) {
if (ref && ref.length != null) {
setTimeout(() => {
if (ref.length > 0) {
try {
ref[0].$el.querySelector('.ant-input').focus()
} catch (e) {
console.error('获取输入框焦点失败:', e)
}
}
}, 100)
}
},
handleHostname (hostname) {
if (this.isNotHostname(hostname)) {
return ''
}
// 移除所有空白符
return hostname.replaceAll(/\s+/g, '')
},
isNotHostname (hostname) {
// 暂时只判断数字
return !hostname || /^[\d\s]+$/.test(hostname)
},
},
}
================================================
FILE: packages/gui/src/view/pages/help.vue
================================================
帮助中心
反馈问题
查看日志
================================================
FILE: packages/gui/src/view/pages/index.vue
================================================
给开发者的辅助工具
安装根证书
{{ update.progress }}%{{ update.downloading ? '新版本下载中' : (`检查更新${update.checking ? '中' : ''}`) }}
如果它解决了你的问题,请不要吝啬你的star哟!点这里
================================================
FILE: packages/gui/src/view/pages/plugin/git.vue
================================================
Git.exe代理设置
仅针对git命令行的代理设置,github网站的访问无需设置
随应用启动
当前已启动
当前未启动
关闭sslVerify
安装Git时未选择使用系统证书管理服务时必须关闭
Git.exe将不代理以下仓库;可以是根地址、组织/机构地址、完整地址
================================================
FILE: packages/gui/src/view/pages/plugin/node.vue
================================================
NPM加速
由于nodejs不走系统证书,所以npm加速不是很好用,可以用淘宝registry
随应用启动
当前已启动
当前未启动
如果你的npm命令改成了其他名字,或者想设置绿色版npm程序路径,可在此处修改
关闭strict-ssl
npm代理启用后必须关闭
npmjs原生
taobao镜像
设置后立即生效,即使关闭 ds 也会继续保持
yarn原生
taobao镜像
设置后立即生效,即使关闭 ds 也会继续保持
自动设置,启动npm加速开关时将会设置如下环境变量
某些库需要自己设置镜像变量,才能下载,比如:electron
================================================
FILE: packages/gui/src/view/pages/plugin/overwall.vue
================================================
梯子
原理说明
启用
这是什么功能?你懂的!偷偷的用,别声张。注:请不要看视频,流量挺小的!
建议参照右上角的原理说明,自建二层代理服务端,并在此页下方配置代理服务端。
声明:此功能仅供技术学习与探讨!
启用PAC
PAC内收录了常见的被封杀的域名
当里面某些域名你不想被拦截时,你可以配置这些域名为禁用,也可以关闭PAC
是否自动更新PAC
开启自动更新后,启动代理服务时,将会异步从下面的远程地址下载PAC文件到本地。
注:只要下载成功后,即使关闭自动更新功能,也会优先读取最近下载的文件!
远程PAC文件内容可以是base64编码格式,也可以是未经过编码的
PAC没有拦截到的域名,可以在此处定义;配置为禁用时,将不使用梯子
{{ item2.label }}
Nginx二层代理服务端配置
您可以在此处配置自己的代理服务器地址。
警告:请勿使用来源不明的服务器地址,有安全风险!
================================================
FILE: packages/gui/src/view/pages/plugin/pip.vue
================================================
PIP加速
如果你的pip命令改成了其他名字(如pip3),或想设置绿色版pip程序路径,可在此处修改
原生
aliyun镜像
北京外国语大学镜像
南京大学镜像
清华大学镜像
百度镜像
中科大镜像
豆瓣镜像
搜狐镜像
华中科大镜像
山东理工大学镜像
设置后立即生效,即使关闭 ds 也会继续保持
使用以上域名安装包时,不会进行SSL证书验证,多个域名用空格隔开
注意:切换仓库镜像同时会修改pip.ini中的trusted-host配置,即使关闭 ds 也会继续保持
================================================
FILE: packages/gui/src/view/pages/proxy.vue
================================================
系统代理设置
随应用启动
当前已启动
当前未启动
是否代理HTTP请求
勾选时,同时代理HTTP和HTTPS请求;不勾选时,只代理HTTPS请求
提示:仅为了加速访问Github网站的用户,建议不勾选。
是否同时修改HTTPS_PROXY环境变量(不好用,不建议勾选)
当发现某些应用并没有走加速通道或加速报错时,可尝试勾选此选项,并重新开启系统代理开关
注意:当前已打开的命令行并不会实时生效,需要重新打开一个新的命令行窗口
去设置
解决OneNote、MicrosoftStore、Outlook等UWP应用开启代理后无法访问网络的问题
是否排除国内域名白名单
国内域名白名单内收录了国内可直接访问的域名,这些域名将不被代理
当里面某些域名你希望代理时,你可以配置这些域名为不排除,也可以关闭国内域名白名单
是否自动更新国内域名白名单
开启自动更新并启动系统代理时,将会异步从下面的远程地址下载国内域名白名单文件到本地。
注:只要下载成功后,即使关闭自动更新功能,也会优先读取最近下载的文件!
远程国内域名白名单文件内容可以是base64编码格式,也可以是未经过编码的
国内域名不包含的域名,可以在此处定义;配置为 不排除时,将被代理
{{ item2.label }}
设置Loopback
打开EnableLoopback
1、此设置用于解决OneNote、MicrosoftStore、Outlook等UWP应用无法访问网络的问题。
2、点击右上方按钮,打开EnableLoopback,然后按下图所示操作即可
================================================
FILE: packages/gui/src/view/pages/server.vue
================================================
加速服务设置
随应用启动
当前已启动
当前未启动
你可以设置为0.0.0.0,让其他电脑可以使用此代理服务
修改后需要重启应用
NODE_TLS_REJECT_UNAUTHORIZED
高风险操作,没有特殊情况请勿关闭
校验加速目标网站的ssl证书
如果目标网站证书有问题,但你想强行访问,可以临时关闭此项
启用拦截
关闭拦截,且关闭增强功能时,就不需要安装根证书,退化为安全模式
允许插入并运行脚本
关闭后,Github油猴脚本也将关闭
请求: ms,对应timeout配置
连接: ms,对应keepAliveTimeout配置
这里指定域名的超时时间:(域名配置可使用通配符或正则)
配置为不代理的域名不会通过代理
{{ item2.label }}
说明:自动兼容程序会自动根据错误信息进行兼容性调整,并将兼容设置保存在 ~/.dev-sidecar/automaticCompatibleConfig.json 文件中。但并不是所有的兼容设置都是正确的,所以需要通过以下配置来覆盖错误的兼容设置。
提示:IP预设置功能,优先级高于 DNS设置
(域名配置可使用通配符或正则)
这里配置哪些域名需要通过国外DNS服务器获取IP进行访问
{{ item.value }}
启用
ms
使用以下DNS获取IP进行测速
以下域名在启动后立即进行测速,其他域名在第一次访问时才测速
立即重新测速
刷新
{{ element.host }} {{ element.time ? `${element.time}ms` : (element.title ? '' : '测速中') }} {{ element.dns }}
================================================
FILE: packages/gui/src/view/pages/setting.vue
================================================
设置
查看日志
本应用开机自启
关闭窗口时隐藏Dock图标(仅限Mac)
修改后需要重启应用
启用远程配置
应用启动时会向下面的地址请求配置补丁,获得最新的优化后的github访问体验。
如果您觉得远程配置有安全风险,请关闭此功能,或删除共享远程配置,仅使用个人远程配置。
配置优先级:本地修改配置 > 个人远程配置 > 共享远程配置 > 默认配置
重载远程配置
注意,部分远程配置文件所在站点,修改内容后可能需要等待一段时间才能生效。
如果重载远程配置后发现下载的还是修改前的内容,请稍等片刻再重试。
亮色
暗色
跟随系统
显示
隐藏
是否显示首页的警告提示
弹出提示
直接退出
最小化到系统托盘
点击窗口右上角关闭按钮的效果
部分快捷键已被占用:F5、F12、Ctrl+F、F3、Shift+F3
打开窗口
隐藏窗口
启动软件时,是否打开窗口。提示:如果设置为隐藏窗口,可点击系统托盘小图标打开窗口。
×
开启
关闭
开启自动检查更新后,每次应用启动时会检查一次更新,如有新版本,则会弹出提示。
忽略
不忽略
预发布版本号为带有 “-” 的版本。注:该配置只对当前版本为正式版本时有效。
修改后,重启DS才生效!
注意:原目录中的文件不会自动转移到新的目录,请自行转移或删除。
{{ item.label }}
修改后,重启DS才生效!
单个日志文件的大小限制,达到限制时会执行备份和清理程序;配置为0时,表示不限制大小。
修改后,重启DS才生效,隔天或达到日志文件大小限制时,才会触发清理程序!
================================================
FILE: packages/gui/src/view/router/index.js
================================================
import Index from '../pages/index'
import Git from '../pages/plugin/git'
import Node from '../pages/plugin/node'
import Overwall from '../pages/plugin/overwall'
import Pip from '../pages/plugin/pip'
import Proxy from '../pages/proxy'
import Server from '../pages/server'
import Setting from '../pages/setting'
import Help from '../pages/help'
const routes = [
{ path: '/', redirect: '/index' },
{ path: '/index', component: Index },
{ path: '/server', component: Server },
{ path: '/proxy', component: Proxy },
{ path: '/setting', component: Setting },
{ path: '/help', component: Help },
{ path: '/plugin/node', component: Node },
{ path: '/plugin/git', component: Git },
{ path: '/plugin/pip', component: Pip },
{ path: '/plugin/overwall', component: Overwall },
]
export default routes
================================================
FILE: packages/gui/src/view/router/menu.js
================================================
export default function createMenus (app) {
const plugins = [
{ title: 'NPM加速', path: '/plugin/node', icon: 'like' },
{ title: 'Git.exe代理', path: '/plugin/git', icon: 'github' },
{ title: 'PIP加速', path: '/plugin/pip', icon: 'bulb' },
]
const menus = [
{ title: '首页', path: '/index', icon: 'home' },
{ title: '加速服务', path: '/server', icon: 'thunderbolt' },
{ title: '系统代理', path: '/proxy', icon: 'deployment-unit' },
{ title: '设置', path: '/setting', icon: 'setting' },
{ title: '帮助中心', path: '/help', icon: 'star' },
{
title: '应用',
path: '/plugin',
icon: 'api',
children: plugins,
},
]
if (app.$global && app.$global.setting && app.$global.setting.overwall) {
plugins.push({ title: '增强功能', path: '/plugin/overwall', icon: 'global' })
}
return menus
}
================================================
FILE: packages/gui/src/view/status.js
================================================
import lodash from 'lodash'
import Vue from 'vue'
const status = {
server: {
enabled: false,
},
proxy: {
enabled: false,
},
plugin: {
node: {},
},
}
async function install (api) {
api.ipc.on('status', (event, message) => {
console.log('view on status', event, message)
const value = message.value
const key = message.key
lodash.set(status, key, value)
})
const basicStatus = await api.status.get()
lodash.merge(status, basicStatus)
Vue.prototype.$status = status
return status
}
export default {
install,
status,
}
================================================
FILE: packages/gui/src/view/style/index.scss
================================================
.footer-bar {
padding: 10px;
text-align: right;
border-top: #eee 1px solid;
}
.flex-l-r {
align-content: center;
display: flex;
justify-content: flex-end;
align-items: center;
& > a {
align-content: center;
display: flex;
align-items: center;
}
}
span.ant-input {
white-space: nowrap;
overflow: hidden;
vertical-align: middle;
}
.mr10 {
margin-right: 10px;
}
.mt-1 {
margin-top: -1px;
}
.mt-2 {
margin-top: -2px;
}
.mt10 {
margin-top: 10px;
}
.mt20 {
margin-top: 20px;
}
.ml5 {
margin-left: 5px;
}
.ml10 {
margin-left: 10px;
}
ol {
margin-block-start: 0em;
margin-block-end: 0em;
padding-inline-start: 20px;
}
.form-help {
font-size: 12px;
line-height: 15px;
color: #a1a1a1;
i {
font-family: 'Microsoft YaHei', serif;
font-style: normal;
font-weight: bold;
}
code {
padding: 0 0.4em;
}
}
code {
font-size: 85%;
font-style: normal;
border-radius: 6px;
color: #888;
background-color: #f1f1f1;
margin-left: 0.2em;
margin-right: 0.2em;
padding: 0.2em 0.4em;
white-space: break-spaces;
}
.ace_search_form .ace_searchbtn {
width: auto;
min-width: 27px;
}
.ant-radio-button-wrapper {
margin-bottom: 3px;
}
.ant-form-item-control {
line-height: 37px;
}
hr {
border-width: 2px 0 0 0;
border-style: solid;
border-color: #eee;
width: 100%;
}
.ant-modal-content {
background-color: #fbfbfb;
}
.restore-factory-settings {
div {
padding-left: 1em;
}
span {
display: inline-block;
background-color: #eee;
padding: 2px 5px;
margin: 0 5px 5px 5px;
}
}
.help-list {
ul {
padding-left: 10px;
li {
list-style: none;
line-height: 30px;
div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
}
a {
color: #3990e0;
}
a:hover {
text-decoration: underline;
color: #2c9be5;
}
}
// 嵌套列表
ul {
padding-left: 20px;
}
}
ul:first-child li:first-child div:first-child.title1 {
margin-top: 0;
}
.title1 {
font-size: 18px;
font-weight: bold;
border-bottom: 1px solid #eee;
margin-top: 12px;
margin-bottom: 5px;
padding-bottom: 5px;
padding-left: 5px;
}
.title2 {
font-size: 16px;
font-weight: bold;
margin-top: 10px;
}
.console {
font-family: Consolas, arial, serif;
}
}
================================================
FILE: packages/gui/src/view/style/theme/dark.scss
================================================
/* 暗色主题 */
$dark-logo: url('../../../../public/logo/logo-lang-light.svg');
$dark-bg: #1e1f22; //背景
$dark-bg-highlight: #333; //高亮块:背景
$dark-text: #ddd; //字体颜色
$dark-bd: #333; //边框和分隔线
$dark-btn: #444; //按钮:边框和背景颜色
$dark-input: #777; //输入框:背景色
.theme-dark {
hr {
border-color: $dark-bd;
}
/* 背景色和字体颜色 */
.ds_layout,
.ant-layout,
.ds-container,
.ds-container .container-header,
.ant-layout-footer {
background-color: $dark-bg;
color: $dark-text;
}
div,
span,
label {
color: $dark-text;
}
.form-help {
color: #a1a1a1;
}
code {
color: #bbb;
background-color: #333;
}
/* 高亮块:背景色和字体颜色 */
/* 警告类型 */
.ant-alert-warning {
background-color: $dark-bg-highlight;
border-color: $dark-bg-highlight;
color: $dark-text;
/* 关闭图标颜色 */
.ant-alert-close-icon .anticon-close {
color: $dark-text;
}
}
/* 消息类型 */
.ant-alert-info {
background-color: $dark-bg-highlight;
border-color: $dark-bg-highlight;
color: $dark-text;
}
/* 边框和分隔线 */
.ant-layout-has-sider,
.ant-layout-sider-children,
.ds-container .container-header,
.logo,
.footer-bar,
.ant-layout-footer,
.ant-tabs .ant-tabs-left-bar,
.ant-tabs .ant-tabs-left-content {
border-color: $dark-bd;
}
.ant-radio-button-wrapper:not(:first-child)::before {
background-color: #666;
}
.ant-divider {
background-color: $dark-bd;
}
.help-list .title1 {
border-bottom-color: $dark-bd;
}
/* 左侧 */
/** 背景色 **/
.ant-layout-sider {
background-color: $dark-bg;
}
/** Logo **/
.logo {
background-image: $dark-logo; /* logo使用亮色的 */
}
/** 菜单 **/
.ant-menu {
background-color: $dark-bg;
color: $dark-text;
}
/* 菜单选中时,或鼠标移到菜单上时的样式 */
.ant-menu-item:hover,
.ant-menu-submenu .ant-menu-submenu-title:hover,
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background-color: $dark-bg-highlight;
color: #1890ff;
span {
color: #1890ff;
}
}
/* 输入框、下拉框 */
.ant-input,
.ant-input-number-input,
.ant-input-number,
.ant-select-selection,
.ant-input-group-addon {
background-color: $dark-input;
border-color: #aaa;
color: $dark-text;
&:hover,
&:focus {
border-color: #eee;
}
&:focus {
box-shadow: 0 0 0 2px rgb(255 255 255 / 35%);
}
}
/* 选中的下拉框 */
.ant-select-open,
.ant-select-focused {
.ant-select-selection {
box-shadow: 0 0 0 2px rgb(255 255 255 / 35%);
}
}
/* 卡片消息:IP测速 */
.ant-card {
background-color: $dark-input;
border-color: $dark-input;
.ant-card-head {
border-bottom-color: #929292;
}
}
/* 标签:未启用 */
.ant-tag-red {
background-color: #4f4749;
border-color: #4f4749;
color: #bf8285;
}
/* 标签:已启用 */
.ant-tag-green {
background-color: #505f5f;
border-color: #505f5f;
color: #90cb9f;
}
/* 标签:警告 */
.ant-tag-orange {
background-color: #5a5750;
border-color: #5a5750;
color: #cfa572;
}
/* 标签:未知 */
.ant-tag:not(.ant-tag-red, .ant-tag-green, .ant-tag-orange) {
background-color: #5a5a5a;
border-color: #5a5a5a;
color: #ccc;
}
/* 按钮 */
.ant-btn:not(.ant-btn-danger, .ant-btn-primary) {
background-color: $dark-btn;
border-color: $dark-btn;
color: $dark-text;
&:hover {
opacity: 0.8;
}
}
/* 单选框:开关式 */
.ant-switch:not(.ant-switch-checked) {
background-color: $dark-btn;
border-color: $dark-btn;
&:hover {
opacity: 0.8;
}
}
/* 单选框:按钮式 */
.ant-radio-button-wrapper {
background-color: $dark-btn;
border-color: $dark-btn;
color: $dark-text;
&:hover {
opacity: 0.8;
}
}
/* JSON编辑器:应用于拦截设置 */
.jsoneditor-vue {
/*整个编辑框:背景色和边框*/
div.jsoneditor {
background-color: $dark-bg-highlight;
border: none;
}
/* 头部菜单栏:边框 */
div.jsoneditor-menu {
background-color: $dark-bg-highlight;
border-color: $dark-bg-highlight;
}
/* 内容区域左边:行号 */
.ace_gutter {
background-color: #444;
.ace_gutter-cell {
color: #aaa;
}
}
/* 内容区域右边:JSON内容 */
.ace_scroller {
background-color: #555;
}
/* key的颜色 */
.ace_variable,
.ace_text-layer {
color: #eee;
}
/* 字符串值的颜色 */
.ace_string,
.ace_cjk {
color: #a6eaa6;
}
.ace_constant {
/* 数字的颜色 */
&.ace_numeric {
color: #ec9999;
}
/* 布尔值的颜色 */
&.ace_language {
color: #f4c995;
}
}
/* 当前行高亮样式 */
.ace_gutter-active-line,
.ace_marker-layer .ace_active-line {
background-color: #838774;
}
/* 选中行高亮样式 */
.ace-jsoneditor {
.ace_marker-layer .ace_selection {
background-color: #8b2929; /* 同时应用于当前选中的搜索结果项的背景色,建议与搜索结果边框颜色保持一致 */
}
/* 光标颜色 */
.ace_cursor {
border-left-color: #ddd;
}
}
/* 搜索框 */
.ace_button,
button,
.ace_search_field {
color: #000;
}
/* 搜索结果 */
.ace-jsoneditor .ace_marker-layer .ace_selected-word {
border-color: #8b2929;
}
}
.search-bar {
div {
color: #000;
}
span {
color: #000;
}
}
}
================================================
FILE: packages/gui/vue.config.js
================================================
const path = require('node:path')
const { defineConfig } = require('@vue/cli-service')
const webpack = require('webpack')
const publishUrl = process.env.VUE_APP_PUBLISH_URL
const publishProvider = process.env.VUE_APP_PUBLISH_PROVIDER
console.log('Publish url:', publishUrl)
module.exports = defineConfig({
pages: {
index: {
entry: 'src/main.js',
title: 'DevSidecar-给开发者的边车辅助工具',
},
},
lintOnSave: false,
configureWebpack: {
plugins: [
new webpack.DefinePlugin({ 'global.GENTLY': true }),
],
module: {
rules: [
{
test: /\.json5$/i,
loader: 'json5-loader',
options: {
esModule: false,
},
type: 'javascript/auto',
},
],
},
},
pluginOptions: {
electronBuilder: {
mainProcessFile: './src/background.js',
// Ref: https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1891
customFileProtocol: './',
externals: [
'@starknt/sysproxy',
'@starknt/sysproxy-win32-ia32-msvc',
'@starknt/sysproxy-win32-x64-msvc',
'@starknt/sysproxy-win32-arm64-msvc',
'@starknt/sysproxy-linux-x64-gnu',
'@starknt/sysproxy-linux-arm64-gnu',
'@starknt/sysproxy-darwin-x64',
'@starknt/sysproxy-darwin-arm64',
'@starknt/shutdown-handler-napi',
'@starknt/shutdown-handler-napi-win32-ia32-msvc',
'@starknt/shutdown-handler-napi-win32-x64-msvc',
'@starknt/shutdown-handler-napi-win32-arm64-msvc',
'@starknt/shutdown-handler-napi-linux-x64-gnu',
'@starknt/shutdown-handler-napi-linux-arm64-gnu',
'@starknt/shutdown-handler-napi-darwin-x64',
'@starknt/shutdown-handler-napi-darwin-arm64',
],
nodeIntegration: true,
// Provide an array of files that, when changed, will recompile the main process and restart Electron
// Your main process file will be added by default
mainProcessWatch: ['src/bridge', 'src/*.js', 'node_modules/dev-sidecar/src'],
builderOptions: {
afterPack: './pkg/after-pack.js',
afterAllArtifactBuild: './pkg/after-all-artifact-build.js',
// artifactBuildCompleted: './pkg/artifact-build-completed.js',
// builderOptions: {
// publish: ['github']// 此处写入github 就好,不用添加其他内容
// },
extraResources: [
{
from: 'extra',
to: 'extra',
},
],
appId: 'dev-sidecar',
productName: 'dev-sidecar',
// eslint-disable-next-line no-template-curly-in-string
artifactName: 'DevSidecar-${version}-${arch}.${ext}',
copyright: 'Copyright © 2020-2025 Greper, WangLiang',
nsis: {
oneClick: false,
perMachine: true,
allowElevation: true,
allowToChangeInstallationDirectory: true,
},
win: {
icon: 'build/icons/',
target: [
{
target: 'nsis',
arch: ['x64', 'ia32', 'arm64'],
},
],
// requestedExecutionLevel: 'highestAvailable', // 加了这个无法开机自启
},
linux: {
icon: 'build/mac/',
target: [
{
target: 'deb',
arch: ['x64', 'arm64', 'armv7l'],
},
{
target: 'AppImage',
arch: ['x64', 'arm64', 'armv7l'],
},
{
target: 'tar.gz',
arch: ['x64', 'arm64', 'armv7l'],
},
],
category: 'System',
},
mac: {
icon: './build/mac/icon.icns',
target: {
target: 'dmg',
arch: ['x64', 'arm64', 'universal'],
},
category: 'public.app-category.developer-tools',
},
publish: {
provider: publishProvider,
url: publishUrl,
// url: 'http://dev-sidecar.docmirror.cn/update/preview/',
},
},
chainWebpackMainProcess (config) {
config.entry('mitmproxy').add(path.join(__dirname, 'src/bridge/mitmproxy.js'))
},
},
},
})
================================================
FILE: packages/mitmproxy/LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: packages/mitmproxy/index.js
================================================
module.exports = require('./src')
================================================
FILE: packages/mitmproxy/package.json
================================================
{
"name": "@docmirror/mitmproxy",
"version": "2.0.1",
"private": false,
"description": "",
"author": "docmirror.cn",
"license": "MPL-2.0",
"keywords": [
"dev-sidecar"
],
"main": "src/index.js",
"scripts": {
"test": "mocha"
},
"dependencies": {
"@docmirror/dev-sidecar": "workspace:*",
"agentkeepalive": "^4.5.0",
"axios": "^1.7.7",
"baidu-aip-sdk": "^4.16.16",
"dns-over-http": "^0.2.0",
"is-browser": "^2.1.0",
"json5": "^2.2.3",
"lodash": "^4.17.21",
"lru-cache": "^7.15.0",
"node-forge": "^1.3.1",
"stream-throttle": "^0.1.3",
"through2": "^4.0.2",
"tunnel-agent": "^0.6.0"
}
}
================================================
FILE: packages/mitmproxy/src/index.js
================================================
const mitmproxy = require('./lib/proxy')
const proxyConfig = require('./lib/proxy/common/config')
const speedTest = require('./lib/speed/index.js')
const ProxyOptions = require('./options')
const log = require('./utils/util.log.server')
const { fireError, fireStatus } = require('./utils/util.process')
let servers = []
const api = {
async start (config) {
const proxyOptions = ProxyOptions(config)
const setting = config.setting
if (setting) {
if (setting.userBasePath) {
proxyConfig.setDefaultCABasePath(setting.userBasePath)
}
}
if (proxyOptions.setting && proxyOptions.setting.NODE_TLS_REJECT_UNAUTHORIZED === false) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
} else {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'
}
// log.info('启动代理服务时的配置:', JSON.stringify(proxyOptions, null, '\t'))
const newServers = mitmproxy.createProxy(proxyOptions, (server, port, host, ssl) => {
fireStatus(true)
log.info(`代理服务已启动:${host}:${port}, ssl: ${ssl}`)
})
for (const newServer of newServers) {
newServer.on('close', () => {
log.info('server will closed ')
if (servers.includes(newServer)) {
servers = servers.filter(item => item !== newServer)
if (servers.length === 0) {
fireStatus(false)
}
}
})
newServer.on('error', (e) => {
log.error('server error', e)
// newServer = null
fireError(e)
})
}
servers = newServers
registerProcessListener()
},
async close () {
return new Promise((resolve, reject) => {
if (servers && servers.length > 0) {
for (const server of servers) {
server.close((err) => {
if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') {
if (err.code === 'ERR_SERVER_NOT_RUNNING') {
log.info('代理服务未运行,无需关闭')
resolve()
} else {
log.error('代理服务关闭失败:', err)
reject(err)
}
return
}
log.info('代理服务关闭成功')
resolve()
})
}
servers = []
} else {
log.info('server is null, no need to close.')
fireStatus(false)
resolve()
}
})
},
}
function registerProcessListener () {
process.on('message', (msg) => {
log.info('child get msg:', JSON.stringify(msg))
if (msg.type === 'action') {
api[msg.event.key](msg.event.params)
} else if (msg.type === 'speed') {
speedTest.action(msg.event)
}
})
process.on('SIGINT', () => {
log.info('on sigint : closed ')
process.exit(0)
})
// 避免异常崩溃
process.on('uncaughtException', (err) => {
if (err.code === 'ECONNABORTED') {
// log.error(err.errno)
return
}
log.error('Process uncaughtException:', err)
})
process.on('unhandledRejection', (err, p) => {
log.info('Process unhandledRejection at: Promise', p, 'err:', err)
// application specific logging, throwing an error, or other logic here
})
process.on('uncaughtExceptionMonitor', (err, origin) => {
log.info('Process uncaughtExceptionMonitor:', err, origin)
})
process.on('exit', (code, signal) => {
log.info('代理服务进程被关闭:', code, signal)
})
process.on('beforeExit', (code, signal) => {
log.info('Process beforeExit event with code: ', code, signal)
})
process.on('SIGPIPE', (code, signal) => {
log.warn('sub Process SIGPIPE', code, signal)
})
}
module.exports = {
...api,
config: proxyConfig,
log,
speedTest,
}
================================================
FILE: packages/mitmproxy/src/json.js
================================================
const logOrConsole = require('@docmirror/dev-sidecar/src/utils/util.log-or-console')
let JSON5 = require('json5')
if (JSON5.default) {
JSON5 = JSON5.default
}
module.exports = {
parse (str, defaultValue) {
if (str == null || str.length < 2) {
return defaultValue || {}
}
str = str.toString()
if (defaultValue != null) {
try {
return JSON5.parse(str)
} catch (e) {
logOrConsole.error(`JSON5解析失败: ${e.message},JSON内容:\r\n`, str)
return defaultValue
}
} else {
return JSON5.parse(str)
}
},
stringify (obj) {
return JSON.stringify(obj, null, '\t')
},
// 仅用于记录日志时使用
stringify2 (obj) {
try {
return JSON.stringify(obj)
} catch {
try {
return JSON5.stringify(obj)
} catch {
return obj
}
}
},
}
================================================
FILE: packages/mitmproxy/src/lib/choice/RequestCounter.js
================================================
const { ChoiceCache } = require('./index')
module.exports = new ChoiceCache()
================================================
FILE: packages/mitmproxy/src/lib/choice/index.js
================================================
const LRUCache = require('lru-cache')
const log = require('../../utils/util.log.server')
const cacheSize = 1024
class ChoiceCache {
constructor () {
this.cache = new LRUCache({
maxSize: cacheSize,
sizeCalculation: () => {
return 1
},
})
}
get (key) {
return this.cache.get(key)
}
getOrCreate (key, backupList) {
log.info('get counter:', key)
let item = this.cache.get(key)
if (item == null) {
item = new DynamicChoice(key)
item.setBackupList(backupList)
this.cache.set(key, item)
}
return item
}
}
class DynamicChoice {
constructor (key) {
this.key = key
this.countMap = {} /* ip -> count { value, total, error, keepErrorCount, successRate } */
this.value = null // 当前使用的host
this.backupList = [] // 备选host列表
this.createTime = new Date()
}
doRank () {
// 将count里面根据成功率排序
const countList = []
for (const key in this.countMap) {
countList.push(this.countMap[key])
}
// 将countList根据成功率排序
countList.sort((a, b) => {
return b.successRate - a.successRate
})
log.info('Do rank:', JSON.stringify(countList))
const newBackupList = countList.map(item => item.value)
this.setBackupList(newBackupList)
}
/**
* 设置新的backup列表
* @param newBackupList 新的backupList
*/
setBackupList (newBackupList) {
this.backupList = newBackupList
let defaultTotal = newBackupList.length
for (const ip of newBackupList) {
if (!this.countMap[ip]) {
this.countMap[ip] = { value: ip, total: defaultTotal, error: 0, keepErrorCount: 0, successRate: 0.5 }
defaultTotal--
}
}
this.value = newBackupList.shift()
this.doCount(this.value, false)
}
countStart (value) {
this.doCount(value, false)
}
/**
* 换下一个
* @param count 计数器
*/
changeNext (count) {
log.info('切换backup', count, this.backupList)
count.keepErrorCount = 0 // 清空连续失败
count.total = 0
count.error = 0
const valueBackup = this.value
if (this.backupList.length > 0) {
this.value = this.backupList.shift()
log.info(`切换backup完成: ${this.key}, ip: ${valueBackup} ➜ ${this.value}, this:`, this)
} else {
this.value = null
log.info(`切换backup完成: ${this.key}, backupList为空了,设置this.value: from '${valueBackup}' to null. this:`, this)
}
}
/**
* 记录使用次数或错误次数
* @param ip
* @param isError
*/
doCount (ip, isError) {
let count = this.countMap[ip]
if (count == null) {
count = this.countMap[ip] = { value: ip, total: 5, error: 0, keepErrorCount: 0, successRate: 1 }
}
if (isError) {
// 失败次数+1,累计连续失败次数+1
count.error++
count.keepErrorCount++
} else {
// 总次数+1
count.total++
}
// 计算成功率
count.successRate = 1.0 - (count.error / count.total)
if (isError && this.value === ip) {
// 连续错误3次,切换下一个
if (count.keepErrorCount >= 3) {
this.changeNext(count)
}
// 成功率小于40%,切换下一个
if (count.successRate < 0.4) {
this.changeNext(count)
}
}
}
}
module.exports = {
DynamicChoice,
ChoiceCache,
}
================================================
FILE: packages/mitmproxy/src/lib/dns/base.js
================================================
const LRUCache = require('lru-cache')
const log = require('../../utils/util.log.server')
const matchUtil = require('../../utils/util.match')
const { DynamicChoice } = require('../choice/index')
function mapToList (ipMap) {
const ipList = []
for (const key in ipMap) {
const value = ipMap[key]
if (value && value !== 'false' && value !== '0') { // 配置为 ture 时才生效
ipList.push(key)
}
}
return ipList
}
const defaultCacheSize = 1024
class IpCache extends DynamicChoice {
constructor (hostname) {
super(hostname)
this.lookupCount = 0
}
/**
* 设置新的ipList
*
* @param newBackupList
*/
setBackupList (newBackupList) {
super.setBackupList(newBackupList)
this.lookupCount++
}
}
module.exports = class BaseDNS {
constructor (dnsName, dnsType, cacheSize, preSetIpList) {
this.dnsName = dnsName
this.dnsType = dnsType
this.preSetIpList = preSetIpList
this.cache = new LRUCache({
maxSize: (cacheSize > 0 ? cacheSize : defaultCacheSize),
sizeCalculation: () => {
return 1
},
})
}
count (hostname, ip, isError = true) {
const ipCache = this.cache.get(hostname)
if (ipCache) {
ipCache.doCount(ip, isError)
}
}
async lookup (hostname, ipChecker) {
try {
let ipCache = this.cache.get(hostname)
if (ipCache) {
const ip = ipCache.value
if (ip != null) {
if (ipChecker && ipChecker(ip)) {
ipCache.doCount(ip, false)
return ip
} else {
return hostname
}
}
} else {
ipCache = new IpCache(hostname)
this.cache.set(hostname, ipCache)
}
const t = Date.now()
let ipList = await this._lookupWithPreSetIpList(hostname)
if (ipList == null) {
// 没有获取到ipv4地址
ipList = []
}
ipList.push(hostname) // 把原域名加入到统计里去
ipCache.setBackupList(ipList)
const ip = ipCache.value
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ip} (${Date.now() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
if (ipChecker) {
if (ip != null && ip !== hostname && ipChecker(ip)) {
return ip
}
for (const ip of ipList) {
if (ip !== hostname && ipChecker(ip)) {
return ip
}
}
}
return ip != null ? ip : hostname
} catch (error) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error)
return hostname
}
}
async _lookupWithPreSetIpList (hostname) {
if (this.preSetIpList) {
// 获取当前域名的预设IP列表
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖
} else {
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
}
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList.isPreSet = true
log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
return hostnamePreSetIpList
}
}
}
return await this._lookup(hostname)
}
async _lookup (hostname) {
const start = Date.now()
let response
try {
// 执行DNS查询
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query start: ${hostname}`)
response = await this._doDnsQuery(hostname, 'A', start)
} catch {
// 异常日志在 _doDnsQuery已经打印过,这里就不再打印了
return []
}
try {
const cost = Date.now() - start
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query end: ${hostname}, cost: ${cost} ms, response:`, response)
if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) {
log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response)
return []
}
const ret = response.answers.filter(item => item.type === 'A').map(item => item.data)
if (ret.length === 0) {
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`)
} else {
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IP地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
}
return ret
} catch (e) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] 解读响应失败,response:`, response, ', error:', e)
return []
}
}
_doDnsQuery (hostname, type = 'A', start) {
if (start == null) {
start = Date.now()
}
return new Promise((resolve, reject) => {
// 设置超时任务
let isOver = false
const timeout = 8000
const timeoutId = setTimeout(() => {
if (!isOver) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询超时, hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms`)
reject(new Error('DNS查询超时'))
}
}, timeout)
try {
this._dnsQueryPromise(hostname, type)
.then((response) => {
isOver = true
clearTimeout(timeoutId)
resolve(response)
})
.catch((e) => {
isOver = true
clearTimeout(timeoutId)
if (e.message === 'DNS查询超时') {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询超时. hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms`)
} else {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询错误, hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
}
reject(e)
})
} catch (e) {
isOver = true
clearTimeout(timeoutId)
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询异常, hostname: ${hostname}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
reject(e)
}
})
}
}
================================================
FILE: packages/mitmproxy/src/lib/dns/https.js
================================================
const { promisify } = require('node:util')
const doh = require('dns-over-http')
const BaseDNS = require('./base')
const HttpsAgent = require('../proxy/common/ProxyHttpsAgent')
const Agent = require('../proxy/common/ProxyHttpAgent')
const dohQueryAsync = promisify(doh.query)
function createAgent (dnsServer) {
return new (dnsServer.startsWith('https:') ? HttpsAgent : Agent)({
keepAlive: true,
timeout: 4000,
})
}
module.exports = class DNSOverHTTPS extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerName) {
super(dnsName, 'HTTPS', cacheSize, preSetIpList)
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerName = dnsServerName
}
_dnsQueryPromise (hostname, type = 'A') {
// 请求参数
const options = {
url: this.dnsServer,
agent: createAgent(this.dnsServer),
}
if (this.dnsServerName) {
// 设置SNI
options.servername = this.dnsServerName
options.rejectUnauthorized = false
}
// DNS查询参数
const questions = [
{
type,
name: hostname,
},
]
return dohQueryAsync(options, questions)
}
}
================================================
FILE: packages/mitmproxy/src/lib/dns/index.js
================================================
const matchUtil = require('../../utils/util.match')
const log = require('../../utils/util.log.server')
const DNSOverPreSetIpList = require('./preset.js')
const DNSOverHTTPS = require('./https.js')
const DNSOverTLS = require('./tls.js')
const DNSOverTCP = require('./tcp.js')
const DNSOverUDP = require('./udp.js')
module.exports = {
initDNS (dnsProviders, preSetIpList) {
const dnsMap = {}
// 创建普通的DNS
for (const provider in dnsProviders) {
const conf = dnsProviders[provider]
// 获取DNS服务器
let server = conf.server || conf.host
if (server != null) {
server = server.replace(/\s+/, '')
}
if (!server) {
continue
}
// 获取DNS类型
let type = conf.type
if (type == null) {
if (server.startsWith('https://') || server.startsWith('http://')) {
type = 'https'
} else if (server.startsWith('tls://') || server.startsWith('dot://')) {
type = 'tls'
} else if (server.startsWith('tcp://')) {
type = 'tcp'
} else if (server.includes('://') && !server.startsWith('udp://')) {
throw new Error(`Unknown type DNS: ${server}, provider: ${provider}`)
} else {
type = 'udp'
}
} else {
type = type.replace(/\s+/, '').toLowerCase()
}
// 创建DNS对象
if (type === 'https' || type === 'doh' || type === 'dns-over-https') {
if (!server.includes('/')) {
server = `https://${server}/dns-query`
}
// 基于 https
dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server, conf.sni || conf.servername)
} else {
// 获取DNS端口
let port = conf.port
// 处理带协议的DNS服务地址
if (server.includes('://')) {
server = server.split('://')[1]
}
// 处理带端口的DNS服务地址
if (port == null && server.includes(':')) {
[server, port] = server.split(':')
}
if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
// 基于 tls
dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.sni || conf.servername)
} else if (type === 'tcp') {
// 基于 tcp
dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
} else {
// 基于 udp
dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port)
}
}
if (conf.forSNI || conf.forSni) {
dnsMap.ForSNI = dnsMap[provider]
}
}
// 创建预设IP的DNS
dnsMap.PreSet = new DNSOverPreSetIpList(preSetIpList)
if (dnsMap.ForSNI == null) {
dnsMap.ForSNI = dnsMap.PreSet
}
log.info(`设置SNI默认使用的DNS为 '${dnsMap.ForSNI.dnsName}'(注:当某个域名配置了SNI但未配置DNS时,将默认使用该DNS)`)
return dnsMap
},
hasDnsLookup (dnsConfig, hostname) {
// 先匹配 预设IP配置
const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList(hasDnsLookup)')
if (hostnamePreSetIpList) {
return dnsConfig.dnsMap.PreSet
}
// 再匹配 DNS映射配置
const providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName')
// 由于DNS中的usa已重命名为cloudflare,所以做以下处理,为了向下兼容
if (providerName === 'usa' && dnsConfig.dnsMap.usa == null && dnsConfig.dnsMap.cloudflare != null) {
return dnsConfig.dnsMap.cloudflare
}
if (providerName) {
return dnsConfig.dnsMap[providerName]
}
},
}
================================================
FILE: packages/mitmproxy/src/lib/dns/preset.js
================================================
const BaseDNS = require('./base')
module.exports = class DNSOverPreSetIpList extends BaseDNS {
constructor (preSetIpList) {
super('PreSet', 'PreSet', null, preSetIpList)
}
async _lookup (_hostname) {
return []
}
}
================================================
FILE: packages/mitmproxy/src/lib/dns/tcp.js
================================================
const net = require('node:net')
const { Buffer } = require('node:buffer')
const dnsPacket = require('dns-packet')
const randi = require('random-int')
const BaseDNS = require('./base')
const defaultPort = 53 // TCP类型的DNS服务默认端口号
module.exports = class DNSOverTCP extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
super(dnsName, 'TCP', cacheSize, preSetIpList)
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
}
_dnsQueryPromise (hostname, type = 'A') {
return new Promise((resolve, reject) => {
// 构造 DNS 查询报文
const packet = dnsPacket.encode({
flags: dnsPacket.RECURSION_DESIRED,
type: 'query',
id: randi(0x0, 0xFFFF),
questions: [{
type,
name: hostname,
}],
})
// --- TCP 查询 ---
const tcpClient = net.createConnection({
host: this.dnsServer,
port: this.dnsServerPort,
}, () => {
// TCP DNS 报文前需添加 2 字节长度头
const lengthBuffer = Buffer.alloc(2)
lengthBuffer.writeUInt16BE(packet.length)
tcpClient.write(Buffer.concat([lengthBuffer, packet]))
})
tcpClient.once('data', (data) => {
const length = data.readUInt16BE(0)
const response = dnsPacket.decode(data.subarray(2, 2 + length))
resolve(response)
tcpClient.end()
})
tcpClient.once('error', (err) => {
reject(err)
tcpClient.end()
})
})
}
}
================================================
FILE: packages/mitmproxy/src/lib/dns/tls.js
================================================
const dnstls = require('./util/dns-over-tls')
const BaseDNS = require('./base')
const defaultPort = 853
module.exports = class DNSOverTLS extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) {
super(dnsName, 'TLS', cacheSize, preSetIpList)
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
this.dnsServerName = dnsServerName
}
_dnsQueryPromise (hostname, type = 'A') {
const options = {
host: this.dnsServer,
port: this.dnsServerPort,
servername: this.dnsServerName || this.dnsServer,
rejectUnauthorized: !this.dnsServerName,
name: hostname,
klass: 'IN',
type,
timeout: 4000,
}
return dnstls.query(options)
}
}
================================================
FILE: packages/mitmproxy/src/lib/dns/udp.js
================================================
const dgram = require('node:dgram')
const dnsPacket = require('dns-packet')
const randi = require('random-int')
const BaseDNS = require('./base')
const defaultPort = 53 // UDP类型的DNS服务默认端口号
module.exports = class DNSOverUDP extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
super(dnsName, 'UDP', cacheSize, preSetIpList)
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']')
this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
}
_dnsQueryPromise (hostname, type = 'A') {
return new Promise((resolve, reject) => {
let isOver = false
const timeout = 5000
let timeoutId = null
// 构造 DNS 查询报文
const packet = dnsPacket.encode({
flags: dnsPacket.RECURSION_DESIRED,
type: 'query',
id: randi(0x0, 0xFFFF),
questions: [{
type,
name: hostname,
}],
})
// 创建客户端
const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
isOver = true
clearTimeout(timeoutId)
const response = dnsPacket.decode(msg)
resolve(response)
udpClient.close()
})
// 发送 UDP 查询
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
if (err) {
isOver = true
clearTimeout(timeoutId)
reject(err)
udpClient.close()
}
})
// 设置超时任务
timeoutId = setTimeout(() => {
if (!isOver) {
reject(new Error('DNS查询超时'))
udpClient.close()
}
}, timeout)
})
}
}
================================================
FILE: packages/mitmproxy/src/lib/dns/util/dns-over-tls.js
================================================
/**
* 由于组件 `dns-over-tls@0.0.9` 不支持 `rejectUnauthorized` 和 `timeout` 两个参数,所以将源码复制过来,并简化了代码。
*/
const dnsPacket = require('dns-packet')
const tls_1 = require('node:tls')
const randi = require('random-int')
const TWO_BYTES = 2
function getDnsQuery ({ type, name, klass, id }) {
return {
id,
type: 'query',
flags: dnsPacket.RECURSION_DESIRED,
questions: [{ class: klass, name, type }],
}
}
function query ({ host, servername, type, name, klass, port, rejectUnauthorized, timeout }) {
return new Promise((resolve, reject) => {
if (!host || !servername || !name) {
throw new Error('At least host, servername and name must be set.')
}
let response = Buffer.alloc(0)
let packetLength = 0
const dnsQuery = getDnsQuery({ id: randi(0x0, 0xFFFF), type, name, klass })
const dnsQueryBuf = dnsPacket.streamEncode(dnsQuery)
const socket = tls_1.connect({ host, port, servername, rejectUnauthorized, timeout })
// 超时处理
let isFinished = false
let interval
if (timeout > 0) {
interval = setInterval(() => {
if (!isFinished) {
socket.destroy((...args) => {
console.info('socket destory callback args:', args)
})
reject(new Error('DNS查询超时'))
}
}, timeout)
}
socket.on('secureConnect', () => socket.write(dnsQueryBuf))
socket.on('data', (data) => {
if (timeout) {
isFinished = true
clearInterval(interval)
}
if (response.length === 0) {
packetLength = data.readUInt16BE(0)
if (packetLength < 12) {
reject(new Error('Below DNS minimum packet length (DNS Header is 12 bytes)'))
}
response = Buffer.from(data)
} else {
response = Buffer.concat([response, data])
}
if (response.length === packetLength + TWO_BYTES) {
socket.destroy()
resolve(dnsPacket.streamDecode(response))
} else {
reject(new Error('响应长度不正确'))
}
})
socket.on('error', (err) => {
if (timeout) {
isFinished = true
clearInterval(interval)
}
reject(err)
})
})
}
exports.query = query
exports.default = { query }
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/OPTIONS.js
================================================
const defaultAllowHeaders = '*'
const defaultAllowMethods = 'GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH' // CONNECT、TRACE被认为是不安全的请求,通常不建议允许跨域
function readConfig (config, defaultConfig) {
if (config) {
if (Object.isArray(config)) {
config = config.join(',')
}
} else {
config = defaultConfig
}
return config
}
module.exports = {
name: 'options',
priority: 101,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
// 不是 OPTIONS 请求,或请求头中不含 origin 时,跳过当前拦截器
if (rOptions.method !== 'OPTIONS' || rOptions.headers.origin == null) {
return
}
// 从请求头中获取跨域相关信息;如果不存在,则从配置中获取的值;如果还不存在,则使用默认值
const allowHeaders = rOptions.headers['access-control-request-headers'] || readConfig(interceptOpt.optionsAllowHeaders, defaultAllowHeaders)
const allowMethods = rOptions.headers['access-control-request-method'] || readConfig(interceptOpt.optionsAllowMethods, defaultAllowMethods)
const headers = {
// 允许跨域
'DS-Interceptor': 'options',
'Access-Control-Allow-Origin': rOptions.headers.origin,
'Access-Control-Allow-Headers': allowHeaders,
'Access-Control-Allow-Methods': allowMethods,
'Access-Control-Max-Age': interceptOpt.optionsMaxAge > 0 ? interceptOpt.optionsMaxAge : 2592000, // 默认有效一个月
'Date': new Date().toUTCString(),
}
// 判断是否允许
if (interceptOpt.optionsCredentials !== false && interceptOpt.optionsCredentials !== 'false') {
headers['Access-Control-Allow-Credentials'] = 'true'
}
res.writeHead(200, headers)
res.end()
log.info('options intercept:', (rOptions.original || rOptions).url)
return true // true代表请求结束
},
is (interceptOpt) {
return !!interceptOpt.options
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/abort.js
================================================
module.exports = {
name: 'abort',
priority: 103,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
if (interceptOpt.abort === true || interceptOpt.abort === 'true') {
const headers = {
'Content-Type': 'text/plain; charset=utf-8',
'DS-Interceptor': 'abort',
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(403, headers)
res.write(
'DevSidecar 403: Request abort.\n\n'
+ ' This request is matched by abort intercept.\n\n'
+ ' 因配置abort拦截器,本请求直接返回403禁止访问。',
)
res.end()
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('abort intercept:', url)
return true // true代表请求结束
} else {
const response = interceptOpt.abort
// status
const status = response.status || 403
response.status = status
// body
const body = response.html || response.json || response.script || response.css || response.text || response.body
|| `DevSidecar ${status}: Request abort.\n\n`
+ ' This request is matched by abort intercept.\n\n'
+ ` 因配置abort拦截器,本请求直接返回${status}禁止访问。`
// headers
const headers = response.headers || {}
response.headers = headers
headers['DS-Interceptor'] = 'abort'
// headers.Content-Type
if (status !== 204) {
// (1)如果没有Content-Type,根据response的内容自动设置
if (!headers['Content-Type']) {
if (response.html != null) {
headers['Content-Type'] = 'text/html'
} else if (response.json != null) {
headers['Content-Type'] = 'application/json'
} else if (response.script != null) {
headers['Content-Type'] = 'application/javascript'
} else if (response.css != null) {
headers['Content-Type'] = 'text/css'
} else {
headers['Content-Type'] = 'text/plain'
}
}
// (2)如果Content-Type没有charset,自动设置为utf-8
if (headers['Content-Type'] != null && !headers['Content-Type'].includes('charset')) {
headers['Content-Type'] += '; charset=utf-8'
}
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin && !headers['Access-Control-Allow-Origin']) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(status, headers)
if (status !== 204) {
res.write(body)
}
res.end()
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('abort intercept:', url, ', response:', JSON.stringify(response))
return true // true代表请求结束
}
},
is (interceptOpt) {
return !!interceptOpt.abort
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/baiduOcr.js
================================================
function getTomorrow () {
const now = new Date()
const tomorrow = new Date(now)
// 设置日期为明天
tomorrow.setDate(now.getDate() + 1)
// 重置时间为凌晨 0 点 0 分 0 秒
tomorrow.setHours(0, 0, 0, 0)
return tomorrow.getTime()
}
// function getNextMonth () {
// const now = new Date()
// const currentYear = now.getFullYear()
// const currentMonth = now.getMonth()
//
// // 如果当前月份是12月,年份增加1,并且月份设为0(1月)
// const nextMonth = (currentMonth + 1) % 12
// const nextYear = nextMonth === 0 ? currentYear + 1 : currentYear
//
// return new Date(nextYear, nextMonth, 1, 0, 0, 0, 0).getTime()
// }
const AipOcrClient = require('baidu-aip-sdk').ocr
const AipOcrClientMap = {}
const apis = [
'accurateBasic', // 调用通用文字识别(高精度版)
'accurate', // 调用通用文字识别(含位置高精度版)
'handwriting', // 手写文字识别
]
const limitMap = {}
function createBaiduOcrClient (config) {
const key = config.id
if (AipOcrClientMap[key]) {
return AipOcrClientMap[key]
}
const client = new AipOcrClient(config.id, config.ak, config.sk)
AipOcrClientMap[key] = client
return client
}
let count = 0
function getConfig (interceptOpt, tryCount, log) {
tryCount = tryCount || 1
let config
if (typeof (interceptOpt.baiduOcr) && interceptOpt.baiduOcr.length > 0) {
config = interceptOpt.baiduOcr[count++ % interceptOpt.baiduOcr.length]
if (tryCount < interceptOpt.baiduOcr.length) {
if (!config || !config.id || !config.ak || !config.sk) {
return getConfig(interceptOpt, tryCount + 1, log) // 递归找到有效的配置
}
}
// 避免count值过大,造成问题
if (count >= 100000) {
count = 0
}
} else {
config = interceptOpt.baiduOcr
tryCount = null // 将tryCount设置为null代表只有一个配置
}
if (!config || !config.id || !config.ak || !config.sk) {
return null // 没有配置或配置错误,直接返回null
}
// 获取当前配置可用的API
for (let i = 0; i < apis.length; i++) {
const api = apis[i]
if (!checkIsLimitConfig(config.id, api)) {
config.api = api
break
}
log.warn(`百度云账号 ${config.id} 的接口 ${api} 已超出限额`)
}
// 如果当前配置的所有API均不可用,则返回null
if (config.api == null) {
if (tryCount == null) {
return null // 只配置了一个账号,没有更多账号可以选择了,直接返回null
} else {
if (tryCount < interceptOpt.baiduOcr.length) {
// 递归找到有效的配置
return getConfig(interceptOpt, tryCount + 1, log)
} else {
return null
}
}
}
return config
}
function limitConfig (id, api) {
const key = `${id}_${api}`
limitMap[key] = getTomorrow()
// limitMap[key] = Date.now() + 5000 // 测试用,5秒后解禁
}
function checkIsLimitConfig (id, api) {
const key = `${id}_${api}`
const limitTime = limitMap[key]
return limitTime && limitTime > Date.now()
}
module.exports = {
name: 'baiduOcr',
priority: 131,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
const headers = {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
}
// 获取配置
const config = getConfig(interceptOpt, null, log)
if (!config) {
res.writeHead(200, headers)
res.write('{"error_code": 99917, "error_msg": "dev-sidecar中,未配置百度云账号,或所有百度云账号的免费额度都已用完!!!"}')
res.end()
return true
}
if (!config.id || !config.ak || !config.sk) {
res.writeHead(200, headers)
res.write('{"error_code": 999500, "error_msg": "dev-sidecar中,baiduOcr的 id 或 ak 或 sk 配置为空"}')
res.end()
return true
}
headers['DS-Interceptor'] = `baiduOcr: id=${config.id}, api=${config.api || apis[0]}, account=${config.account}`
// 获取图片的base64编码
let imageBase64 = rOptions.path.substring(rOptions.path.indexOf('?') + 1)
if (!imageBase64) {
res.writeHead(200, headers)
res.write('{"error_code": 999400, "error_msg": "图片Base64参数为空"}')
res.end()
return true
}
imageBase64 = decodeURIComponent(imageBase64)
// 调用百度云 “文字识别” 相关接口,根据 `config.api` 调用不同的接口
const client = createBaiduOcrClient(config)
const options = {
recognize_granularity: 'big',
detect_direction: 'false',
paragraph: 'false',
probability: 'false',
...(config.options || {}),
}
log.info('发起百度ocr请求', req.hostname)
client[config.api || apis[0]](imageBase64, options).then((result) => {
if (result.error_code != null) {
log.error('baiduOcr error:', result)
if (result.error_code === 17) {
// 当前百度云账号,达到当日调用次数上限
limitConfig(config.id, config.api)
log.error(`当前百度云账号的接口 ${config.api},已达到当日调用次数上限,暂时禁用它,明天会自动放开:`, config)
}
} else {
log.info('baiduOcr success:', result)
}
res.writeHead(200, headers)
res.write(JSON.stringify(result)) // 格式如:{"words_result":[{"words":"6525"}],"words_result_num":1,"log_id":1818877093747960000}
res.end()
if (next) {
next() // 异步执行完继续next
}
}).catch((err) => {
log.error('baiduOcr error:', err)
res.writeHead(200, headers)
res.write(`{"error_code": 999500, "error_msg": "${err}"}`) // 格式如:{"words_result":[{"words":"6525"}],"words_result_num":1,"log_id":1818877093747960000}
res.end()
if (next) {
next() // 异步执行完继续next
}
})
log.info('proxy baiduOcr: hostname:', req.hostname)
return 'no-next'
},
is (interceptOpt) {
return !!interceptOpt.baiduOcr
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/cacheRequest.js
================================================
function getMaxAge (interceptOpt) {
// 秒
if (interceptOpt.cacheSeconds > 0 || interceptOpt.cacheMaxAge > 0 || interceptOpt.cache > 0) {
return interceptOpt.cacheSeconds || interceptOpt.cacheMaxAge || interceptOpt.cache
}
// 分钟
if (interceptOpt.cacheMinutes > 0) {
return interceptOpt.cacheMinutes * 60 // 60:1分钟
}
// 小时
if (interceptOpt.cacheHours > 0) {
return interceptOpt.cacheHours * 3600 // 60 * 60 一小时
}
// 天
if (interceptOpt.cacheDays > 0) {
return interceptOpt.cacheDays * 86400 // 60 * 60 * 24 一天
}
// 星期
if (interceptOpt.cacheWeeks > 0) {
return interceptOpt.cacheWeeks * 604800 // 60 * 60 * 24 * 7 一周
}
// 月
if (interceptOpt.cacheMonths > 0) {
return interceptOpt.cacheMonths * 2592000 // 60 * 60 * 24 * 30 一个月
}
// 年
if (interceptOpt.cacheYears > 0) {
return interceptOpt.cacheYears * 31536000 // 60 * 60 * 24 * 365 一年
}
return null
}
// 获取 lastModifiedTime 的方法
function getLastModifiedTimeFromIfModifiedSince (rOptions, log) {
// 获取 If-Modified-Since 和 If-None-Match 用于判断是否命中缓存
const lastModified = rOptions.headers['if-modified-since']
if (lastModified == null || lastModified.length === 0) {
return null // 没有lastModified,返回null
}
try {
// 尝试解析 lastModified,并获取time
return new Date(lastModified).getTime()
} catch (e) {
// 为数字时,直接返回
if (/\\d+/.test(lastModified)) {
return lastModified - 0
}
log.warn(`cache intercept: 解析 if-modified-since 失败: '${lastModified}', error:`, e)
}
return null
}
module.exports = {
name: 'cacheRequest',
priority: 104,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
if (rOptions.method !== 'GET') {
return // 非GET请求,不拦截
}
// 获取 Cache-Control 用于判断是否禁用缓存
const cacheControl = rOptions.headers['cache-control']
if (cacheControl && (cacheControl.includes('no-cache') || cacheControl.includes('no-store'))) {
return // 当前请求指定要禁用缓存,跳过当前拦截器
}
// 获取 Pragma 用于判断是否禁用缓存
const pragma = rOptions.headers.pragma
if (pragma && (pragma.includes('no-cache') || pragma.includes('no-store'))) {
return // 当前请求指定要禁用缓存,跳过当前拦截器
}
// 最近编辑时间
const lastModifiedTime = getLastModifiedTimeFromIfModifiedSince(rOptions, log)
if (lastModifiedTime == null) {
return // 没有 lastModified,不拦截
}
// 获取maxAge配置
const maxAge = getMaxAge(interceptOpt)
// 判断缓存是否已过期
const passTime = Date.now() - lastModifiedTime
if (passTime > maxAge * 1000) {
return // 缓存已过期,不拦截
}
// 缓存未过期,直接拦截请求并响应304
res.writeHead(304, {
'DS-Interceptor': `cache: ${maxAge}`,
})
res.end()
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('cache intercept:', url)
return true
},
is (interceptOpt) {
const maxAge = getMaxAge(interceptOpt)
return maxAge != null && maxAge > 0
},
getMaxAge,
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js
================================================
const url = require('node:url')
const lodash = require('lodash')
function replacePlaceholder0 (url, matched, pre) {
if (matched) {
for (let i = 0; i < matched.length; i++) {
url = url.replace(`\${${pre}[${i}]}`, matched[i] || '')
}
if (matched.groups) {
for (const key in matched.groups) {
url = url.replace(`\${${key}}`, matched.groups[key] || '')
}
}
}
return url
}
// 替换占位符
function replacePlaceholder (url, rOptions, pathMatched, hostnameMatched) {
if (url.includes('${')) {
// eslint-disable-next-line no-template-curly-in-string
url = url.replace('${host}', rOptions.hostname)
if (url.includes('${')) {
url = replacePlaceholder0(url, pathMatched, 'p')
url = replacePlaceholder0(url, hostnameMatched, 'h')
}
// 移除多余的占位符
if (url.includes('${')) {
url = url.replace(/\$\{[^}]+\}/g, '')
}
}
return url
}
function buildTargetUrl (rOptions, urlConf, interceptOpt, matched, hostnameMatched) {
let targetUrl
if (interceptOpt && interceptOpt.replace) {
const regexp = new RegExp(interceptOpt.replace)
targetUrl = rOptions.path.replace(regexp, urlConf)
} else if (urlConf.indexOf('http:') === 0 || urlConf.indexOf('https:') === 0) {
targetUrl = urlConf
} else {
let uri = rOptions.path
if (uri.indexOf('http:') === 0 || uri.indexOf('https:') === 0) {
// eslint-disable-next-line node/no-deprecated-api
const URL = url.parse(uri)
uri = URL.path
}
targetUrl = urlConf + uri
}
// 替换占位符
targetUrl = replacePlaceholder(targetUrl, rOptions, matched, hostnameMatched)
// 拼接协议
targetUrl = targetUrl.indexOf('http:') === 0 || targetUrl.indexOf('https:') === 0 ? targetUrl : `${rOptions.protocol}//${targetUrl}`
return targetUrl
}
function doProxy (proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched) {
// 获取代理目标地址
const proxyTarget = buildTargetUrl(rOptions, proxyConf, interceptOpt, matched, hostnameMatched)
// 替换rOptions的属性
// eslint-disable-next-line node/no-deprecated-api
const URL = url.parse(proxyTarget)
rOptions.origional = lodash.cloneDeep(rOptions) // 备份原始请求参数
delete rOptions.origional.agent
delete rOptions.origional.headers
rOptions.protocol = URL.protocol
rOptions.hostname = URL.host
rOptions.host = URL.host
rOptions.headers.host = URL.host
rOptions.path = URL.path
if (URL.port == null) {
rOptions.port = rOptions.protocol === 'https:' ? 443 : 80
}
return proxyTarget
}
module.exports = {
name: 'proxy',
priority: 121,
replacePlaceholder,
buildTargetUrl,
doProxy,
requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) {
const { rOptions, log, RequestCounter } = context
const originHostname = rOptions.hostname
let proxyConf = interceptOpt.proxy
if (RequestCounter && interceptOpt.backup && interceptOpt.backup.length > 0) {
// 优选逻辑
const backupList = [proxyConf]
for (const bk of interceptOpt.backup) {
backupList.push(bk)
}
const key = `${rOptions.hostname}/${interceptOpt.key}`
const count = RequestCounter.getOrCreate(key, backupList)
if (count.value == null) {
count.doRank()
}
if (count.value == null) {
log.error('`count.value` is null, the count:', count)
} else {
count.doCount(count.value)
proxyConf = count.value
context.requestCount = {
key,
value: count.value,
count,
}
}
}
// 替换 rOptions 中的地址,并返回代理目标地址
const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched)
if (context.requestCount) {
log.info('proxy choice:', JSON.stringify(context.requestCount))
}
if (interceptOpt.sni) {
let unVerifySsl = rOptions.agent.options.rejectUnauthorized === false
rOptions.servername = interceptOpt.sni
if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
// rOptions.agent.options.rejectUnauthorized = false // 不能直接在agent上进行修改属性值,因为它采用了单例模式,所有请求共用这个对象的
rOptions.agent = rOptions.agent.unVerifySslAgent
unVerifySsl = true
}
const unVerifySslStr = unVerifySsl ? ', unVerifySsl' : ''
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}, sni: ${interceptOpt.sni}${unVerifySslStr}`)
log.info(`proxy intercept: hostname: ${originHostname}, target: ${proxyTarget}, sni replace servername: ${rOptions.servername}${unVerifySslStr}`)
} else if (interceptOpt.unVerifySsl === true) {
if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
rOptions.agent = rOptions.agent.unVerifySslAgent
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}, unVerifySsl`)
log.info(`proxy intercept: hostname: ${originHostname}, target: ${proxyTarget}, unVerifySsl`)
} else {
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}, already unVerifySsl`)
log.info(`proxy intercept: hostname: ${originHostname}, target: ${proxyTarget}, already unVerifySsl`)
}
} else {
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}`)
log.info(`proxy intercept: hostname: ${originHostname}, target:${proxyTarget}`)
}
return true
},
is (interceptOpt) {
return !!interceptOpt.proxy
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js
================================================
const proxyApi = require('./proxy')
module.exports = {
name: 'redirect',
priority: 105,
requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) {
const { rOptions, log } = context
// 获取重定向目标地址
const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched, hostnameMatched)
const headers = {
'Location': redirect,
'DS-Interceptor': 'redirect',
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(302, headers)
res.end()
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info(`redirect intercept: ${url} ➜ ${redirect}`)
return true // true代表请求结束
},
is (interceptOpt) {
return interceptOpt.redirect // 如果配置中有redirect,那么这个配置是需要redirect拦截的
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/requestReplace.js
================================================
const REMOVE = '[remove]'
function replaceRequestHeaders (rOptions, headers, log) {
for (const key in headers) {
let value = headers[key]
if (value === REMOVE) {
value = null
}
if (value) {
log.debug(`[DS-RequestReplace-Interceptor] replace '${key}': '${rOptions.headers[key.toLowerCase()]}' -> '${value}'`)
rOptions.headers[key.toLowerCase()] = value
} else if (rOptions.headers[key.toLowerCase()]) {
log.debug(`[DS-RequestReplace-Interceptor] remove '${key}': '${rOptions.headers[key.toLowerCase()]}'`)
delete rOptions.headers[key.toLowerCase()]
}
}
log.debug(`[DS-RequestReplace-Interceptor] 最终headers: \r\n${JSON.stringify(rOptions.headers, null, '\t')}`)
}
module.exports = {
name: 'requestReplace',
priority: 111,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
const requestReplaceConfig = interceptOpt.requestReplace
let actions = ''
// 替换请求头
if (requestReplaceConfig.headers) {
replaceRequestHeaders(rOptions, requestReplaceConfig.headers, log)
actions += `${actions ? ',' : ''}headers`
}
// 替换下载文件请求的请求地址(此功能主要是为了方便拦截配置)
// 注:要转换为下载请求,需要 responseReplace 拦截器的配合使用。
if (requestReplaceConfig.doDownload && rOptions.path.match(/DS_DOWNLOAD/i)) {
rOptions.doDownload = true
rOptions.path = rOptions.path.replace(/[?&/]?DS_DOWNLOAD(=[^?&/]+)?$/gi, '')
actions += `${actions ? ',' : ''}path:remove-DS_DOWNLOAD`
}
res.setHeader('DS-RequestReplace-Interceptor', actions)
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('requestReplace intercept:', url)
},
is (interceptOpt) {
return !!interceptOpt.requestReplace
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/sni.js
================================================
module.exports = {
name: 'sni',
priority: 123,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
let unVerifySsl = rOptions.agent.options.rejectUnauthorized === false
rOptions.servername = interceptOpt.sni
if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
// rOptions.agent.options.rejectUnauthorized = false // 不能直接在agent上进行修改属性值,因为它采用了单例模式,所有请求共用这个对象的
rOptions.agent = rOptions.agent.unVerifySslAgent
unVerifySsl = true
}
const unVerifySslStr = unVerifySsl ? ', unVerifySsl' : ''
res.setHeader('DS-Interceptor', `sni: ${interceptOpt.sni}${unVerifySslStr}`)
log.info(`sni intercept: sni replace servername: ${rOptions.hostname} ➜ ${rOptions.servername}${unVerifySslStr}`)
return true
},
is (interceptOpt) {
return !!interceptOpt.sni && !interceptOpt.proxy // proxy生效时,sni不需要生效,因为proxy中也会使用sni覆盖 rOptions.servername
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/success.js
================================================
module.exports = {
name: 'success',
priority: 102,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
if (interceptOpt.success === true || interceptOpt.success === 'true') {
const headers = {
'Content-Type': 'text/plain; charset=utf-8',
'DS-Interceptor': 'success',
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(200, headers)
res.write(
'DevSidecar 200: Request success.\n\n'
+ ' This request is matched by success intercept.\n\n'
+ ' 因配置success拦截器,本请求直接返回200成功。',
)
res.end()
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('success intercept:', url)
return true // true代表请求结束
} else {
const response = interceptOpt.success
// status
const status = response.status || 200
response.status = status
// body
const body = response.html || response.json || response.script || response.css || response.text || response.body
|| `DevSidecar ${status}: Request success.\n\n`
+ ' This request is matched by success intercept.\n\n'
+ ` 因配置success拦截器,本请求直接返回${status}成功。`
// headers
const headers = response.headers || {}
response.headers = headers
headers['DS-Interceptor'] = 'success'
// headers.Content-Type
if (status !== 204) {
// (1)如果没有Content-Type,根据response的内容自动设置
if (!headers['Content-Type']) {
if (response.html != null) {
headers['Content-Type'] = 'text/html'
} else if (response.json != null) {
headers['Content-Type'] = 'application/json'
} else if (response.script != null) {
headers['Content-Type'] = 'application/javascript'
} else if (response.css != null) {
headers['Content-Type'] = 'text/css'
} else {
headers['Content-Type'] = 'text/plain'
}
}
// (2)如果Content-Type没有charset,自动设置为utf-8
if (headers['Content-Type'] != null && !headers['Content-Type'].includes('charset')) {
headers['Content-Type'] += '; charset=utf-8'
}
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin && !headers['Access-Control-Allow-Origin']) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(status, headers)
if (status !== 204) {
res.write(body)
}
res.end()
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('success intercept:', url, ', response:', JSON.stringify(response))
return true // true代表请求结束
}
},
is (interceptOpt) {
return !!interceptOpt.success
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/req/unVerifySsl.js
================================================
module.exports = {
name: 'unVerifySsl',
priority: 124,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
rOptions.agent = rOptions.agent.unVerifySslAgent
log.info(`unVerifySsl intercept: ${rOptions.hostname}, unVerifySsl`)
res.setHeader('DS-Interceptor', 'unVerifySsl')
} else {
log.info(`unVerifySsl intercept: ${rOptions.hostname}, already unVerifySsl`)
res.setHeader('DS-Interceptor', 'already unVerifySsl')
}
return true
},
is (interceptOpt) {
return interceptOpt.unVerifySsl === true || interceptOpt.ssl === false
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/res/AfterOPTIONSHeaders.js
================================================
const responseReplaceApi = require('./responseReplace')
module.exports = {
name: 'AfterOPTIONSHeaders',
desc: '开启了options.js功能时,正常请求时,会需要增加响应头 `Access-Control-Allow-Origin: xxx`',
priority: 201,
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log } = context
if (rOptions.method === 'OPTIONS') {
return
}
const headers = {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Origin': '*',
'Cross-Origin-Resource-Policy': interceptOpt.optionsCrossPolicy || 'cross-origin',
}
// 替换响应头
if (responseReplaceApi.replaceResponseHeaders({ ...headers }, res, proxyRes)) {
log.info('AfterOPTIONSHeaders intercept:', JSON.stringify(headers))
res.setHeader('DS-AfterOPTIONSHeaders-Interceptor', '1')
} else {
res.setHeader('DS-AfterOPTIONSHeaders-Interceptor', '0')
}
},
is (interceptOpt) {
return !!interceptOpt.options
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/res/cacheResponse.js
================================================
const cacheReq = require('../req/cacheRequest')
module.exports = {
name: 'cacheResponse',
priority: 202,
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log } = context
// 只有GET请求
if (rOptions.method !== 'GET') {
return
}
// 判断当前响应码是否不使用缓存
if (interceptOpt.cacheExcludeStatusCodeList && interceptOpt.cacheExcludeStatusCodeList[`${proxyRes.statusCode}`]) {
return
}
// 响应码为 200~303 时才进行缓存(可通过以下两个参数调整范围)
let minStatusCode = interceptOpt.cacheMinStatusCode || 200
let maxStatusCode = interceptOpt.cacheMaxStatusCode || 303
if (minStatusCode > maxStatusCode) {
const temp = minStatusCode
minStatusCode = maxStatusCode
maxStatusCode = temp
}
if (proxyRes.statusCode < minStatusCode || proxyRes.statusCode > maxStatusCode) {
// res.setHeader('DS-Cache-Response-Interceptor', `skip: 'method' or 'status' not match`)
return
}
// 获取maxAge配置
let maxAge = cacheReq.getMaxAge(interceptOpt)
// public 或 private
const cacheControlType = `${interceptOpt.cacheControlType || 'public'}, `
// immutable属性
const cacheImmutable = interceptOpt.cacheImmutable !== false && interceptOpt.cacheImmutable !== 'false' ? ', immutable' : ''
// 获取原响应头中的cache-control、last-modified、expires
const originalHeaders = {
cacheControl: null,
lastModified: null,
expires: null,
etag: null,
}
for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
// 尝试修改rawHeaders中的cache-control、last-modified、expires
if (proxyRes.rawHeaders[i].toLowerCase() === 'cache-control') {
originalHeaders.cacheControl = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
} else if (proxyRes.rawHeaders[i].toLowerCase() === 'last-modified') {
originalHeaders.lastModified = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
} else if (proxyRes.rawHeaders[i].toLowerCase() === 'expires') {
originalHeaders.expires = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
} else if (proxyRes.rawHeaders[i].toLowerCase() === 'etag') {
originalHeaders.etag = { value: proxyRes.rawHeaders[i + 1], valueIndex: i + 1 }
}
// 如果已经设置了cache-control、last-modified、expires,则直接break
if (originalHeaders.cacheControl && originalHeaders.lastModified && originalHeaders.expires && originalHeaders.etag) {
break
}
}
// 判断原max-age是否大于新max-age
if (originalHeaders.cacheControl) {
const maxAgeMatch = originalHeaders.cacheControl.value.match(/max-age=(\d+)/i)
if (maxAgeMatch && Number.parseInt(maxAgeMatch[1]) > maxAge) {
if (interceptOpt.cacheImmutable !== false && !originalHeaders.cacheControl.value.includes('immutable')) {
maxAge = Number.parseInt(maxAgeMatch[1])
} else {
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
res.setHeader('DS-Cache-Response-Interceptor', `skip: ${maxAgeMatch[1]} > ${maxAge}`)
log.info(`cache response intercept: skip: ${maxAgeMatch[1]} > ${maxAge}, url: ${url}`)
return
}
}
}
// 替换用的头信息
const now = new Date()
const replaceHeaders = {
cacheControl: `${cacheControlType}max-age=${maxAge + 1}${cacheImmutable}`,
lastModified: now.toUTCString(),
expires: new Date(now.getTime() + maxAge * 1000).toUTCString(),
}
// 开始替换
// 替换cache-control
if (originalHeaders.cacheControl) {
proxyRes.rawHeaders[originalHeaders.cacheControl.valueIndex] = replaceHeaders.cacheControl
} else {
res.setHeader('Cache-Control', replaceHeaders.cacheControl)
}
// 替换last-modified
if (originalHeaders.lastModified) {
proxyRes.rawHeaders[originalHeaders.lastModified.valueIndex] = replaceHeaders.lastModified
} else {
res.setHeader('Last-Modified', replaceHeaders.lastModified)
}
// 替换expires
if (originalHeaders.expires) {
proxyRes.rawHeaders[originalHeaders.expires.valueIndex] = replaceHeaders.expires
} else {
res.setHeader('Expires', replaceHeaders.expires)
}
res.setHeader('DS-Cache-Response-Interceptor', maxAge)
},
is (interceptOpt) {
const maxAge = cacheReq.getMaxAge(interceptOpt)
return maxAge != null && maxAge > 0
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/res/responseReplace.js
================================================
const lodash = require('lodash')
const cacheReq = require('../req/cacheRequest')
const REMOVE = '[remove]'
// 替换响应头
function replaceResponseHeaders (newHeaders, res, proxyRes) {
if (!newHeaders || lodash.isEmpty(newHeaders)) {
return null
}
// 响应头Key统一转小写
for (const headerKey in newHeaders) {
if (headerKey === headerKey.toLowerCase()) {
continue
}
const value = newHeaders[headerKey]
delete newHeaders[headerKey]
newHeaders[headerKey.toLowerCase()] = value
}
// 原先响应头
const preHeaders = {}
// 替换响应头
const needDeleteKeys = []
for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
const headerKey = proxyRes.rawHeaders[i].toLowerCase()
const newHeaderValue = newHeaders[headerKey]
if (newHeaderValue) {
if (newHeaderValue !== proxyRes.rawHeaders[i + 1]) {
preHeaders[headerKey] = proxyRes.rawHeaders[i + 1] // 先保存原先响应头
if (newHeaderValue === REMOVE) { // 由于拦截配置中不允许配置null,会被删,所以配置一个 "[remove]",当作删除响应头的意思
proxyRes.rawHeaders[i + 1] = ''
} else {
proxyRes.rawHeaders[i + 1] = newHeaderValue
}
}
needDeleteKeys.push(headerKey)
}
}
// 处理删除响应头
for (const headerKey of needDeleteKeys) {
delete newHeaders[headerKey]
}
// 新增响应头
for (const headerKey in newHeaders) {
const headerValue = newHeaders[headerKey]
if (headerValue == null || headerValue === REMOVE) {
continue
}
res.setHeader(headerKey, newHeaders[headerKey])
preHeaders[headerKey] = null // 标记原先响应头为null
}
if (lodash.isEmpty(preHeaders)) {
return null
}
// 返回原先响应头
return preHeaders
}
module.exports = {
name: 'responseReplace',
priority: 203,
replaceResponseHeaders,
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log } = context
if (proxyRes.statusCode !== 200) {
return
}
const responseReplaceConfig = interceptOpt.responseReplace
let actions = ''
const replaceHeaders = responseReplaceConfig.headers || {}
// 处理文件下载请求
if (responseReplaceConfig.doDownload || rOptions.doDownload) {
const filename = (rOptions.path.match('^.*/([^/?]+)/?(\\?.*)?$') || [])[1] || 'UNKNOWN_FILENAME'
// 设置文件下载响应头
replaceHeaders['content-disposition'] = `attachment; filename="${encodeURIComponent(filename)}"`
// 设置文件类型
if (replaceHeaders['content-type'] == null) {
replaceHeaders['content-type'] = 'application/octet-stream'
}
// 如果未手动配置需要缓存,则不允许使用缓存
const maxAge = cacheReq.getMaxAge(interceptOpt)
if (maxAge == null || maxAge <= 0) {
replaceHeaders['cache-control'] = REMOVE
replaceHeaders['last-modified'] = REMOVE
replaceHeaders.expires = REMOVE
}
actions += `${actions ? ',' : ''}download:${filename}`
}
// 替换响应头
if (replaceResponseHeaders(replaceHeaders, res, proxyRes)) {
actions += `${actions ? ',' : ''}headers`
}
if (actions) {
res.setHeader('DS-ResponseReplace-Interceptor', actions)
log.info(`response intercept: ${actions}`)
}
},
is (interceptOpt) {
return !!interceptOpt.responseReplace
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/impl/res/script.js
================================================
const monkey = require('../../../monkey')
// const CryptoJs = require('crypto-js')
const lodash = require('lodash')
const log = require('../../../../utils/util.log.server')
const SCRIPT_URL_PRE = '/____ds_script____/' // 内置脚本的请求地址前缀
const SCRIPT_PROXY_URL_PRE = '/____ds_script_proxy____/' // 绝对地址脚本的伪脚本地址前缀
const REMOVE = '[remove]' // 标记需要移除的头信息
function getScript (key, script) {
const scriptUrl = SCRIPT_URL_PRE + key
// const hash = CryptoJs.SHA256(script).toString(CryptoJs.enc.Base64)
// return ``
return ``
}
function getScriptByUrlOrPath (scriptUrlOrPath) {
return ``
}
module.exports = {
name: 'script',
priority: 211,
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log, setting } = context
// github特殊处理
if (rOptions.hostname === 'github.com' && rOptions.headers['turbo-frame'] === 'repo-content-turbo-frame') {
return
}
// 如果没有响应头 'content-type',或其值不是 'text/html',则不处理
if (!proxyRes.headers['content-type'] || !proxyRes.headers['content-type'].includes('text/html')) {
res.setHeader('DS-Script-Interceptor', 'Not text/html')
return
}
let keys = interceptOpt.script
if (typeof keys === 'string') {
keys = [keys]
}
try {
// 内置脚本列表
const scripts = monkey.get(setting.script.dirAbsolutePath)
let tags = ''
for (const key of keys) {
if (key === 'global' || key === 'tampermonkey') {
continue
}
let scriptTag
if (key.includes('/')) {
scriptTag = getScriptByUrlOrPath(key) // 1.绝对地址或相对地址(注意:当目标站点限制跨域脚本时,可使用相对地址,再结合proxy拦截器进行代理,可规避掉限制跨域脚本问题。)
} else {
const script = scripts[key]
if (script == null) {
continue
}
scriptTag = getScript(key, script.script) // 2.DS内置脚本
}
tags += `\r\n\t${scriptTag}`
}
// 如果脚本为空,则不插入
if (tags === '') {
return
}
// 插入油猴脚本浏览器扩展
if (typeof interceptOpt.tampermonkeyScript === 'string') {
tags = `\r\n\t${getScriptByUrlOrPath(interceptOpt.tampermonkeyScript)}${tags}`
} else {
tags = `\r\n\t${getScript('tampermonkey', scripts.tampermonkey.script)}${tags}`
}
res.setHeader('DS-Script-Interceptor', 'true')
log.info(`script response intercept: insert script ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`, ', head:', tags)
return {
head: `${tags}\r\n`,
}
} catch (err) {
try {
res.setHeader('DS-Script-Interceptor', 'error')
} catch (e) {
// ignore
}
log.error('load monkey script error', err)
}
},
is (interceptOpt) {
return interceptOpt.script
},
// 处理拦截配置:自动生成script拦截器所需的辅助配置,降低使用`script拦截器`配置绝对地址和相对地址时的门槛
handleScriptInterceptConfig (intercepts) {
// 为了简化 script 拦截器配置脚本绝对地址,这里特殊处理一下
for (const hostnamePattern in intercepts) {
const hostnameConfig = intercepts[hostnamePattern]
const scriptProxy = {}
const handleScriptUrl = (scriptUrl, name, replaceScriptUrlFun) => {
if (scriptUrl.indexOf('https:') === 0 || scriptUrl.indexOf('http:') === 0) {
// 绝对地址
const scriptKey = `${SCRIPT_PROXY_URL_PRE + scriptUrl.replace('.js', '').replace(/[\W_]+/g, '_')}.js` // 伪脚本地址:移除 script 中可能存在的特殊字符,并转为相对地址
scriptProxy[scriptKey] = scriptUrl
log.info(`替换${name}配置值:'${scriptUrl}' -> '${scriptKey}'`)
if (typeof replaceScriptUrlFun === 'function') {
replaceScriptUrlFun(scriptKey)
}
} else if (scriptUrl.indexOf('/') === 0) {
// 相对地址
scriptProxy[scriptUrl] = scriptUrl
}
}
for (const pathPattern in hostnameConfig) {
const pathConfig = hostnameConfig[pathPattern]
// 处理 script 配置
if (typeof pathConfig.script === 'object' && pathConfig.script.length > 0) {
for (let i = 0; i < pathConfig.script.length; i++) {
const scriptUrl = pathConfig.script[i]
handleScriptUrl(scriptUrl, 'script', (scriptKey) => {
pathConfig.script[i] = scriptKey
})
}
} else if (typeof pathConfig.script === 'string') {
handleScriptUrl(pathConfig.script, 'script', (scriptKey) => {
pathConfig.script = scriptKey
})
}
// 处理 tampermonkeyScript 配置
if (typeof pathConfig.tampermonkeyScript === 'string') {
handleScriptUrl(pathConfig.tampermonkeyScript, 'tampermonkey', (scriptKey) => {
pathConfig.tampermonkeyScript = scriptKey
})
}
}
// 自动创建脚本
if (!lodash.isEmpty(scriptProxy)) {
for (const scriptKey in scriptProxy) {
if (scriptKey.indexOf(SCRIPT_PROXY_URL_PRE) === 0) {
// 绝对地址:新增代理配置
const scriptUrl = scriptProxy[scriptKey]
const pathPattern = `^${scriptKey.replace(/\./g, '\\.')}$`
if (hostnameConfig[pathPattern]) {
continue // 配置已经存在,按自定义配置优先
}
hostnameConfig[pathPattern] = {
proxy: scriptUrl,
// 移除部分请求头,避免触发目标站点的拦截策略
requestReplace: {
headers: {
host: REMOVE,
referer: REMOVE,
cookie: REMOVE,
},
},
// 替换和移除部分响应头,避免触发目标站点的阻止脚本加载策略
responseReplace: {
headers: {
'content-type': 'application/javascript; charset=utf-8',
'set-cookie': REMOVE,
'server': REMOVE,
},
},
cacheDays: 7,
desc: '为伪脚本文件设置代理地址,并设置响应头 `content-type: \'application/javascript; charset=utf-8\'`,同时缓存7天。',
}
const obj = {}
obj[pathPattern] = hostnameConfig[pathPattern]
log.debug(`域名 '${hostnamePattern}' 拦截配置中,新增伪脚本地址的代理配置:`, JSON.stringify(obj, null, '\t'))
} else {
// 相对地址:新增响应头Content-Type替换配置
if (hostnameConfig[scriptKey]) {
continue // 配置已经存在,按自定义配置优先
}
hostnameConfig[scriptKey] = {
responseReplace: {
headers: {
'content-type': 'application/javascript; charset=utf-8',
},
},
cacheDays: 7,
desc: '为脚本设置响应头 `content-type: \'application/javascript; charset=utf-8\'`,同时缓存7天。',
}
const obj = {}
obj[scriptKey] = hostnameConfig[scriptKey]
log.info(`域名 '${hostnamePattern}' 拦截配置中,新增目标脚本地址的响应头替换配置:`, JSON.stringify(obj, null, '\t'))
}
}
}
}
},
}
================================================
FILE: packages/mitmproxy/src/lib/interceptor/index.js
================================================
// request interceptor impls
const OPTIONS = require('./impl/req/OPTIONS.js')
const success = require('./impl/req/success')
const abort = require('./impl/req/abort')
const cacheRequest = require('./impl/req/cacheRequest')
const redirect = require('./impl/req/redirect')
const requestReplace = require('./impl/req/requestReplace')
const proxy = require('./impl/req/proxy')
const sni = require('./impl/req/sni')
const unVerifySsl = require('./impl/req/unVerifySsl')
const baiduOcr = require('./impl/req/baiduOcr')
// response interceptor impls
const AfterOPTIONSHeaders = require('./impl/res/AfterOPTIONSHeaders')
const cacheResponse = require('./impl/res/cacheResponse')
const responseReplace = require('./impl/res/responseReplace')
const script = require('./impl/res/script')
module.exports = [
// request interceptor impls
OPTIONS, success, abort, cacheRequest, redirect,
requestReplace,
proxy, sni, unVerifySsl,
baiduOcr,
// response interceptor impls
AfterOPTIONSHeaders, cacheResponse, responseReplace,
script,
]
================================================
FILE: packages/mitmproxy/src/lib/monkey/index.js
================================================
const fs = require('node:fs')
const path = require('node:path')
const log = require('../../utils/util.log.server')
let scripts
function buildScript (sc, content, scriptName) {
const scriptKey = `ds_${scriptName}${sc.version ? (`_${sc.version}`) : ''}:`
// 代码1:监听事件
const runAt = sc['run-at'] || 'document-end'
let eventStr
if (runAt === 'document-end') {
eventStr = 'document.addEventListener("DOMContentLoaded"'
} else {
eventStr = 'window.addEventListener("load"'
}
// 代码2:初始化
const options = {
name: sc.name,
version: sc.version,
icon: sc.icon,
}
const initStr = `
const DS_init = (window.__ds_global__ || {})['DS_init']
if (typeof DS_init === 'function') {
\tconsole.log("${scriptKey} do DS_init")
\tDS_init(${JSON.stringify(options)});
} else {
\tconsole.log("${scriptKey} has no DS_init")
}`
// 代码3:判断是否启用了脚本
const checkEnabledStr = `
if (!((window.__ds_global__ || {}).GM_getValue || (() => true))("ds_enabled", true)) {
\tconsole.log("${scriptKey} tampermonkey disabled")
\treturn
}`
// 代码4:`GM_xxx` 方法读取
let grantStr = ''
for (const item of sc.grant) {
if (grantStr.length > 0) {
grantStr += '\r\n'
}
if (item.indexOf('.') > 0) {
grantStr += `${item} = (window.__ds_global__ || {})['${item}'];`
} else {
grantStr += `const ${item} = (window.__ds_global__ || {})['${item}'] || (() => {});`
}
}
// 拼接脚本
return `${eventStr}, () => {${
initStr}\r\n${
checkEnabledStr}\r\n\r\n${
grantStr ? (`${grantStr}\r\n\r\n`) : ''
}${content
}\r\nconsole.log("${scriptKey} completed")`
+ `\r\n})`
+ `\r\nconsole.log("${scriptKey} loaded")`
}
function loadScript (content, scriptName) {
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_notification
const annoFlag = '// ==/UserScript=='
const arr = content.split(annoFlag)
const start = 0
const confStr = arr[start]
const confItemArr = confStr.split('\n')
const sc = {
grant: [],
match: [],
script: '',
}
for (const string of confItemArr) {
const reg = new RegExp('.*@(\\S+)\\s(.+)')
const ret = string.match(reg)
if (ret) {
const key = ret[1].trim()
const value = ret[2].trim()
if (key === 'grant') {
sc.grant.push(value)
} else if (key === 'match') {
sc.match.push(value)
} else {
sc[key] = value
}
}
}
const script = arr[start + 1].trim()
sc.script = buildScript(sc, script, scriptName)
return sc
}
function readFile (rootDir, script) {
log.info('read script, script root location:', path.resolve('./'))
const location = path.join(rootDir, `./${script}`)
log.info('read script, the script location:', location)
return fs.readFileSync(location).toString()
}
const api = {
get (rootDir) {
if (scripts == null) {
return api.load(rootDir)
}
return scripts
},
load (rootDir) {
scripts = {}
scripts.github = loadScript(readFile(rootDir, 'github.script'), 'github')
scripts.google = loadScript(readFile(rootDir, 'google.js'), 'google')
// scripts.jquery = { script: readFile(rootDir, 'jquery.min.js') }
scripts.tampermonkey = { script: readFile(rootDir, 'tampermonkey.script') }
return scripts
},
loadScript,
}
module.exports = api
================================================
FILE: packages/mitmproxy/src/lib/proxy/common/ProxyHttpAgent.js
================================================
const AgentOrigin = require('agentkeepalive')
module.exports = class Agent extends AgentOrigin {
// Hacky
getName (option) {
let name = AgentOrigin.prototype.getName.call(this, option)
name += ':'
if (option.customSocketId) {
name += option.customSocketId
}
return name
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/common/ProxyHttpsAgent.js
================================================
const HttpsAgentOrigin = require('agentkeepalive').HttpsAgent
module.exports = class HttpsAgent extends HttpsAgentOrigin {
// Hacky
getName (option) {
let name = HttpsAgentOrigin.prototype.getName.call(this, option)
name += ':'
if (option.customSocketId) {
name += option.customSocketId
}
return name
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/common/config.js
================================================
const path = require('node:path')
const config = exports
config.defaultHost = '127.0.0.1'
config.defaultPort = 31181
config.defaultMaxLength = 100
config.caCertFileName = 'dev-sidecar.ca.crt'
config.caKeyFileName = 'dev-sidecar.ca.key.pem'
config.caName = 'DevSidecar - This certificate is generated locally'
config.caBasePath = buildDefaultCABasePath()
config.getDefaultCABasePath = function () {
return config.caBasePath
}
config.setDefaultCABasePath = function (path) {
config.caBasePath = path
}
function buildDefaultCABasePath () {
const userHome = process.env.USERPROFILE || process.env.HOME || '/'
return path.resolve(userHome, './.dev-sidecar')
}
config.getDefaultCACertPath = function () {
return path.resolve(config.getDefaultCABasePath(), config.caCertFileName)
}
config.getDefaultCAKeyPath = function () {
return path.resolve(config.getDefaultCABasePath(), config.caKeyFileName)
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/common/util.js
================================================
const url = require('node:url')
const tunnelAgent = require('tunnel-agent')
const log = require('../../../utils/util.log.server')
const matchUtil = require('../../../utils/util.match')
const Agent = require('./ProxyHttpAgent')
const HttpsAgent = require('./ProxyHttpsAgent')
const util = exports
const httpsAgentCache = {}
const httpAgentCache = {}
let socketId = 0
let httpsOverHttpAgent, httpOverHttpsAgent, httpsOverHttpsAgent
function getTimeoutConfig (hostname, serverSetting) {
const timeoutMapping = serverSetting.timeoutMapping
const timeoutConfig = matchUtil.matchHostname(timeoutMapping, hostname, 'get timeoutConfig') || {}
return {
timeout: timeoutConfig.timeout || serverSetting.defaultTimeout || 20000,
keepAliveTimeout: timeoutConfig.keepAliveTimeout || serverSetting.defaultKeepAliveTimeout || 30000,
}
}
function createHttpsAgent (timeoutConfig, verifySsl) {
const key = `${timeoutConfig.timeout}-${timeoutConfig.keepAliveTimeout}`
if (!httpsAgentCache[key]) {
verifySsl = !!verifySsl
// 证书回调函数
const checkServerIdentity = (host, cert) => {
log.info(`checkServerIdentity: ${host}, CN: ${cert.subject.CN}, C: ${cert.subject.C || cert.issuer.C}, ST: ${cert.subject.ST || cert.issuer.ST}, bits: ${cert.bits}`)
}
const agent = new HttpsAgent({
keepAlive: true,
timeout: timeoutConfig.timeout,
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
checkServerIdentity,
rejectUnauthorized: verifySsl,
})
agent.unVerifySslAgent = new HttpsAgent({
keepAlive: true,
timeout: timeoutConfig.timeout,
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
checkServerIdentity,
rejectUnauthorized: false,
})
httpsAgentCache[key] = agent
log.info('创建 HttpsAgent 成功, timeoutConfig:', timeoutConfig, ', verifySsl:', verifySsl)
}
return httpsAgentCache[key]
}
function createHttpAgent (timeoutConfig) {
const key = `${timeoutConfig.timeout}-${timeoutConfig.keepAliveTimeout}`
if (!httpAgentCache[key]) {
httpAgentCache[key] = new Agent({
keepAlive: true,
timeout: timeoutConfig.timeout,
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
})
log.info('创建 HttpAgent 成功, timeoutConfig:', timeoutConfig)
}
return httpAgentCache[key]
}
function createAgent (protocol, timeoutConfig, verifySsl) {
return protocol === 'https:'
? createHttpsAgent(timeoutConfig, verifySsl)
: createHttpAgent(timeoutConfig)
}
util.parseHostnameAndPort = (host, defaultPort) => {
let arr = host.match(/^(\[[^\]]+\])(?::(\d+))?$/) // 尝试解析IPv6
if (arr) {
arr = arr.slice(1)
if (arr[1]) {
arr[1] = Number.parseInt(arr[1], 10)
}
} else {
arr = host.split(':')
if (arr.length > 1) {
arr[1] = Number.parseInt(arr[1], 10)
}
}
if (defaultPort > 0 && (arr.length === 1 || arr[1] === undefined)) {
arr[1] = defaultPort
} else if (arr.length === 2 && arr[1] === undefined) {
arr.pop()
}
return arr
}
util.getOptionsFromRequest = (req, ssl, externalProxy = null, serverSetting, compatibleConfig = null) => {
// eslint-disable-next-line node/no-deprecated-api
const urlObject = url.parse(req.url)
const defaultPort = ssl ? 443 : 80
const protocol = ssl ? 'https:' : 'http:'
const headers = Object.assign({}, req.headers)
let externalProxyUrl = null
if (externalProxy) {
if (typeof externalProxy === 'string') {
externalProxyUrl = externalProxy
} else if (typeof externalProxy === 'function') {
try {
externalProxyUrl = externalProxy(req, ssl)
} catch (e) {
log.error('externalProxy error:', e)
}
}
}
// 解析host和port
const arr = util.parseHostnameAndPort(req.headers.host)
const hostname = arr[0]
const port = arr[1] || defaultPort
delete headers['proxy-connection']
let agent
if (!externalProxyUrl) {
// keepAlive
if (headers.connection !== 'close') {
const timeoutConfig = getTimeoutConfig(hostname, serverSetting)
// log.info(`get timeoutConfig '${hostname}':`, timeoutConfig)
agent = createAgent(protocol, timeoutConfig, serverSetting.verifySsl)
headers.connection = 'keep-alive'
} else {
agent = false
}
} else {
agent = util.getTunnelAgent(protocol === 'https:', externalProxyUrl)
}
// 初始化options
const options = {
protocol,
method: req.method,
url: req.url,
hostname,
port,
path: urlObject.path,
headers: req.headers,
agent,
compatibleConfig,
}
// eslint-disable-next-line node/no-deprecated-api
if (protocol === 'http:' && externalProxyUrl && (url.parse(externalProxyUrl)).protocol === 'http:') {
// eslint-disable-next-line node/no-deprecated-api
const externalURL = url.parse(externalProxyUrl)
options.hostname = externalURL.hostname
options.port = externalURL.port
// support non-transparent proxy
options.path = `http://${urlObject.host}${urlObject.path}`
}
// mark a socketId for Agent to bind socket for NTLM
if (req.socket.customSocketId) {
options.customSocketId = req.socket.customSocketId
} else if (headers.authorization) {
options.customSocketId = req.socket.customSocketId = socketId++
}
return options
}
util.getTunnelAgent = (requestIsSSL, externalProxyUrl) => {
// eslint-disable-next-line node/no-deprecated-api
const urlObject = url.parse(externalProxyUrl)
const protocol = urlObject.protocol || 'http:'
let port = urlObject.port
if (!port) {
port = protocol === 'http:' ? 80 : 443
}
const hostname = urlObject.hostname || 'localhost'
if (requestIsSSL) {
if (protocol === 'http:') {
if (!httpsOverHttpAgent) {
httpsOverHttpAgent = tunnelAgent.httpsOverHttp({
proxy: {
host: hostname,
port,
},
})
}
return httpsOverHttpAgent
} else {
if (!httpsOverHttpsAgent) {
httpsOverHttpsAgent = tunnelAgent.httpsOverHttps({
proxy: {
host: hostname,
port,
},
})
}
return httpsOverHttpsAgent
}
} else {
if (protocol === 'http:') {
// if (!httpOverHttpAgent) {
// httpOverHttpAgent = tunnelAgent.httpOverHttp({
// proxy: {
// host: hostname,
// port: port
// }
// })
// }
return false
} else {
if (!httpOverHttpsAgent) {
httpOverHttpsAgent = tunnelAgent.httpOverHttps({
proxy: {
host: hostname,
port,
},
})
}
return httpOverHttpsAgent
}
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/compatible/compatible.js
================================================
/**
* 自动兼容程序自适应生成配置
* 此脚本会针对各种兼容性问题,为对应域名生成相应的兼容性配置,并将自适应配置写入到 `~/.dev-sidecar/automaticCompatibleConfig.json` 文件中。
* 当然,也有可能会生成错误的配置,导致无法兼容,这时候可以通过 `config.server.compatible` 配置项,来覆盖这里生成的配置,达到主动适配的效果。
*
* @author WangLiang
*/
const fs = require('node:fs')
const jsonApi = require('../../../json')
const log = require('../../../utils/util.log.server')
const matchUtil = require('../../../utils/util.match')
const configLoader = require('@docmirror/dev-sidecar/src/config/local-config-loader')
const defaultConfig = {
// connect阶段所需的兼容性配置
connect: {
// 参考配置
// 'xxx.xxx.xxx.xxx:443': {
// ssl: false
// }
},
// request阶段所需的兼容性配置
request: {
// 参考配置
// 'xxx.xxx.xxx.xxx:443': {
// rejectUnauthorized: false
// }
},
}
const config = _loadFromFile(defaultConfig)
function _getConnectConfig (hostname, port) {
const connectConfig = config.connect[`${hostname}:${port}`]
log.info(`getConnectConfig: ${hostname}:${port}, ${jsonApi.stringify2(connectConfig)}`)
return connectConfig
}
function _getRequestConfig (hostname, port) {
const requestConfig = config.request[`${hostname}:${port}`]
log.info(`getRequestConfig: ${hostname}:${port}, ${jsonApi.stringify2(requestConfig)}`)
return requestConfig
}
// region 本地配置文件所需函数
function _loadFromFile (defaultConfig) {
const configPath = configLoader.getAutomaticCompatibleConfigPath()
let config
if (!fs.existsSync(configPath)) {
config = defaultConfig
log.info(`本地未保存过 ${configPath} 文件,使用默认配置`)
} else {
const file = fs.readFileSync(configPath)
log.info('读取 automaticCompatibleConfig.json 成功:', configPath)
const fileStr = file.toString()
try {
config = jsonApi.parse(fileStr)
if (config.connect == null) {
config.connect = defaultConfig.connect
}
if (config.request == null) {
config.request = defaultConfig.request
}
} catch (e) {
log.error('解析 automaticCompatibleConfig.json 成功:', configPath, ', error:', e)
return defaultConfig
}
}
return config
}
function _saveConfigToFile () {
const filePath = configLoader.getAutomaticCompatibleConfigPath()
try {
fs.writeFileSync(filePath, jsonApi.stringify(config))
log.info('保存 automaticCompatibleConfig.json 成功:', filePath)
} catch (e) {
log.error('保存 automaticCompatibleConfig.json 失败:', filePath, ', error:', e)
}
}
// endregion
module.exports = {
/**
* 获取 connect 阶段所需的兼容性配置
*
* @param hostname 域名
* @param port 端口
* @param manualCompatibleConfig 手动兼容性配置
* @returns connect阶段所需的兼容性配置
*/
getConnectCompatibleConfig (hostname, port, manualCompatibleConfig = null) {
let connectCompatibleConfig = manualCompatibleConfig == null ? null : matchUtil.matchHostname(manualCompatibleConfig.connect, `${hostname}:${port}`, 'getConnectCompatibleConfig')
if (connectCompatibleConfig == null) {
connectCompatibleConfig = _getConnectConfig(hostname, port)
}
return connectCompatibleConfig
},
setConnectSsl (hostname, port, ssl, autoSave = true) {
const connectCompatibleConfig = this.getConnectCompatibleConfig(hostname, port)
if (connectCompatibleConfig) {
connectCompatibleConfig.ssl = ssl
} else {
config.connect[`${hostname}:${port}`] = { ssl }
}
// 配置保存到文件
if (autoSave) {
_saveConfigToFile()
}
log.info(`【自动兼容程序】${hostname}:${port}: 设置 connect.ssl = ${ssl}`)
},
// --------------------------------------------------------------------------------------------------------------------------
/**
* 获取 request 阶段所需的兼容性配置
*
* @param rOptions
* @param manualCompatibleConfig
*/
getRequestCompatibleConfig (rOptions, manualCompatibleConfig = null) {
let requestCompatibleConfig = manualCompatibleConfig == null ? null : matchUtil.matchHostname(manualCompatibleConfig.request, `${rOptions.hostname}:${rOptions.port}`, 'getRequestCompatibleConfig')
if (requestCompatibleConfig == null) {
requestCompatibleConfig = _getRequestConfig(rOptions.hostname, rOptions.port)
}
return requestCompatibleConfig
},
setRequestRejectUnauthorized (rOptions, rejectUnauthorized, autoSave = true) {
const requestCompatibleConfig = this.getRequestCompatibleConfig(rOptions.hostname, rOptions.port)
if (requestCompatibleConfig) {
requestCompatibleConfig.rejectUnauthorized = rejectUnauthorized
} else {
config.request[`${rOptions.hostname}:${rOptions.port}`] = { rejectUnauthorized }
}
// 配置保存到文件
if (autoSave) {
_saveConfigToFile()
}
log.info(`【自动兼容程序】${rOptions.hostname}:${rOptions.port}: 设置 request.rejectUnauthorized = ${rejectUnauthorized}`)
},
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/index.js
================================================
// require('babel-polyfill')
module.exports = require('./mitmproxy')
================================================
FILE: packages/mitmproxy/src/lib/proxy/middleware/InsertScriptMiddleware.js
================================================
const zlib = require('node:zlib')
const through = require('through2')
const log = require('../../../utils/util.log.server')
// 编解码器
const codecMap = {
gzip: {
createCompressor: () => zlib.createGzip(),
createDecompressor: () => zlib.createGunzip(),
},
deflate: {
createCompressor: () => zlib.createDeflate(),
createDecompressor: () => zlib.createInflate(),
},
br: {
createCompressor: () => zlib.createBrotliCompress(),
createDecompressor: () => zlib.createBrotliDecompress(),
},
}
const supportedEncodings = Object.keys(codecMap)
const supportedEncodingsStr = supportedEncodings.join(', ')
const httpUtil = {
// 获取响应内容编码
getContentEncoding (res) {
const encoding = res.headers['content-encoding']
if (encoding) {
return encoding.toLowerCase()
}
return null
},
// 获取编解码器
getCodec (encoding) {
return codecMap[encoding]
},
// 获取支持的编解码器名称字符串
supportedEncodingsStr () {
return supportedEncodingsStr
},
// 是否HTML代码
isHtml (res) {
const contentType = res.headers['content-type']
return (typeof contentType !== 'undefined') && /text\/html|application\/xhtml\+xml/.test(contentType)
},
}
const HEAD = Buffer.from('')
const HEAD_UP = Buffer.from('')
const BODY = Buffer.from('
')
const BODY_UP = Buffer.from('')
function chunkByteReplace (_this, chunk, enc, callback, append) {
if (append) {
if (append.head) {
const ret = injectScriptIntoHtml([HEAD, HEAD_UP], chunk, append.head)
if (ret != null) {
chunk = ret
}
}
if (append.body) {
const ret = injectScriptIntoHtml([BODY, BODY_UP], chunk, append.body)
if (ret != null) {
chunk = ret
}
}
}
_this.push(chunk)
callback()
}
function injectScriptIntoHtml (tags, chunk, script) {
for (const tag of tags) {
const index = chunk.indexOf(tag)
if (index < 0) {
continue
}
const scriptBuf = Buffer.from(script)
const chunkNew = Buffer.alloc(chunk.length + scriptBuf.length)
chunk.copy(chunkNew, 0, 0, index)
scriptBuf.copy(chunkNew, index, 0)
chunk.copy(chunkNew, index + scriptBuf.length, index)
return chunkNew
}
return null
}
function handleResponseHeaders (res, proxyRes) {
Object.keys(proxyRes.headers).forEach((key) => {
if (proxyRes.headers[key] !== undefined) {
// let newkey = key.replace(/^[a-z]|-[a-z]/g, (match) => {
// return match.toUpperCase()
// })
const newkey = key
if (key === 'content-length') {
// do nothing
return
}
if (key === 'content-security-policy') {
// content-security-policy
let policy = proxyRes.headers[key]
const reg = /script-src ([^:]*);/i
const matched = policy.match(reg)
if (matched) {
if (!matched[1].includes('self')) {
policy = policy.replace('script-src', 'script-src \'self\' ')
}
}
res.setHeader(newkey, policy)
return
}
res.setHeader(newkey, proxyRes.headers[key])
}
})
res.writeHead(proxyRes.statusCode)
}
const contextPath = '/____ds_script____/'
const monkey = require('../../monkey')
module.exports = {
requestIntercept (context, req, res, ssl, next) {
const { rOptions, log, setting } = context
if (rOptions.path.indexOf(contextPath) !== 0) {
return
}
const urlPath = rOptions.path
let filename = urlPath.replace(contextPath, '')
// 重命名过,向下兼容
if (filename === 'global') {
filename = 'tampermonkey'
}
const script = monkey.get(setting.script.defaultDir)[filename]
// log.info(`urlPath: ${urlPath}, fileName: ${filename}, script: ${script}`)
log.info('ds_script, filename:', filename, ', `script != null` =', script != null)
const now = new Date()
res.writeHead(200, {
'DS-Middleware': 'ds_script',
'Content-Type': 'application/javascript; charset=utf-8',
'Cache-Control': 'public, max-age=86401, immutable', // 缓存1天
'Last-Modified': now.toUTCString(),
'Expires': new Date(now.getTime() + 86400000).toUTCString(), // 缓存1天
'Date': new Date().toUTCString(),
})
res.write(script.script)
res.end()
return true
},
responseInterceptor (req, res, proxyReq, proxyRes, ssl, next, append) {
if (append == null || (!append.head && !append.body)) {
next()
return
}
const isHtml = httpUtil.isHtml(proxyRes)
const contentLengthIsZero = (() => {
return proxyRes.headers['content-length'] === 0
})()
if (!isHtml || contentLengthIsZero) {
next()
return
}
// 先处理头信息
handleResponseHeaders(res, proxyRes)
// 获取响应内容编码
const encoding = httpUtil.getContentEncoding(proxyRes)
if (encoding) {
// 获取编解码器
const codec = httpUtil.getCodec(encoding)
if (codec) {
proxyRes
.pipe(codec.createDecompressor()) // 解码
.pipe(through(function (chunk, enc, callback) {
// 插入head和body
chunkByteReplace(this, chunk, enc, callback, append)
}))
.pipe(codec.createCompressor()) // 编码
.pipe(res)
} else {
log.error(`InsertScriptMiddleware.responseInterceptor(): 暂不支持编码方式 ${encoding}, 目前支持:`, httpUtil.supportedEncodingsStr())
}
} else {
proxyRes
.pipe(through(function (chunk, enc, callback) {
chunkByteReplace(this, chunk, enc, callback, append)
}))
.pipe(res)
}
next()
},
httpUtil,
handleResponseHeaders,
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/middleware/overwall.js
================================================
const { Buffer } = require('node:buffer')
const fs = require('node:fs')
const path = require('node:path')
const url = require('node:url')
const lodash = require('lodash')
const request = require('request')
const log = require('../../../utils/util.log.server')
const matchUtil = require('../../../utils/util.match')
const pac = require('./source/pac')
const dateUtil = require('@docmirror/dev-sidecar/src/utils/util.date')
let pacClient = null
function matched (hostname, overWallTargetMap) {
// 匹配配置文件
const ret1 = matchUtil.matchHostname(overWallTargetMap, hostname, 'matched overwall')
if (ret1) {
return 'in config'
} else if (ret1 === false || ret1 === 'false') {
log.debug(`域名 ${hostname} 的overwall配置为 false,跳过增强功能,即使它在 pac.txt 里`)
return null
}
// 匹配 pac.txt
if (pacClient == null) {
return null
}
const ret = pacClient.FindProxyForURL(`https://${hostname}`, hostname)
if (ret && ret.indexOf('PROXY ') === 0) {
log.info(`matchHostname: matched overwall: '${hostname}' -> '${ret}' in pac.txt`)
return 'in pac.txt'
} else {
log.debug(`matchHostname: matched overwall: Not-Matched '${hostname}' -> '${ret}' in pac.txt`)
return null
}
}
function getUserBasePath () {
const userHome = process.env.USERPROFILE || process.env.HOME || '/'
return path.resolve(userHome, './.dev-sidecar')
}
// 下载的 pac.txt 文件保存路径
function getTmpPacFilePath () {
return path.join(getUserBasePath(), '/pac.txt')
}
function loadPacLastModifiedTime (pacTxt) {
const matched = pacTxt.match(/(?<=! Last Modified: )[^\r\n]+/g)
if (matched && matched.length > 0) {
try {
return new Date(matched[0])
} catch {
return null
}
}
}
// 保存 pac 内容到 `~/pac.txt` 文件中
function savePacFile (pacTxt) {
const pacFilePath = getTmpPacFilePath()
try {
fs.writeFileSync(pacFilePath, pacTxt)
log.info('保存 pac.txt 文件成功:', pacFilePath)
} catch (e) {
log.error('保存 pac.txt 文件失败:', pacFilePath, ', error:', e)
return
}
// 尝试解析和修改 pac.txt 文件时间
const lastModifiedTime = loadPacLastModifiedTime(pacTxt)
if (lastModifiedTime) {
fs.stat(pacFilePath, (err, _stats) => {
if (err) {
log.error('修改 pac.txt 文件时间失败:', err)
return
}
// 修改文件的访问时间和修改时间为当前时间
fs.utimes(pacFilePath, lastModifiedTime, lastModifiedTime, (utimesErr) => {
if (utimesErr) {
log.error('修改 pac.txt 文件时间失败:', utimesErr)
} else {
log.info(`'${pacFilePath}' 文件的修改时间已更新为其最近更新时间 '${dateUtil.format(lastModifiedTime, false)}'`)
}
})
})
}
}
// 异步下载 pac.txt ,避免影响代理服务的启动速度
async function downloadPacAsync (pacConfig) {
const remotePacFileUrl = pacConfig.pacFileUpdateUrl
log.info('开始下载远程 pac.txt 文件:', remotePacFileUrl)
request(remotePacFileUrl, (error, response, body) => {
if (error) {
log.error(`下载远程 pac.txt 文件失败: ${remotePacFileUrl}, error:`, error, ', response:', response, ', body:', body)
return
}
if (response && response.statusCode === 200) {
if (body == null || body.length < 100) {
log.warn('下载远程 pac.txt 文件成功,但内容为空或内容太短,判断为无效的 pax.txt 文件:', remotePacFileUrl, ', body:', body)
return
} else {
log.info('下载远程 pac.txt 文件成功:', remotePacFileUrl)
}
// 尝试解析Base64(注:https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt 下载下来的是Base64格式)
let pacTxt = body
if (!pacTxt.includes('!---------------------EOF')) {
try {
pacTxt = Buffer.from(pacTxt, 'base64').toString('utf8')
// log.debug('解析 base64 后的 pax:', pacTxt)
} catch {
log.error(`远程 pac.txt 文件内容即不是base64格式,也不是要求的格式,url: ${remotePacFileUrl},body: ${body}`)
return
}
}
// 保存到本地
savePacFile(pacTxt)
} else {
log.error(`下载远程 pac.txt 文件失败: ${remotePacFileUrl}, response:`, response, ', body:', body)
}
})
}
function createOverwallMiddleware (overWallConfig) {
if (!overWallConfig || overWallConfig.enabled !== true) {
return null
}
if (overWallConfig.pac && overWallConfig.pac.enabled) {
// 初始化pac
pacClient = pac.createPacClient(overWallConfig.pac.pacFileAbsolutePath)
}
let server = overWallConfig.server
let keys = Object.keys(server)
if (keys.length === 0) {
server = overWallConfig.serverDefault
keys = Object.keys(server)
}
if (keys.length === 0) {
return null
}
const overWallTargetMap = matchUtil.domainMapRegexply(overWallConfig.targets)
return {
sslConnectInterceptor: (req, _cltSocket, _head) => {
const hostname = req.url.split(':')[0]
return matched(hostname, overWallTargetMap)
},
requestIntercept (context, req, res, _ssl, _next) {
const { rOptions, log, RequestCounter } = context
if (rOptions.protocol === 'http:') {
return
}
const hostname = rOptions.hostname
const matchedResult = matched(hostname, overWallTargetMap)
if (matchedResult == null || matchedResult === false || matchedResult === 'false') {
return
}
const cacheKey = '__over_wall_proxy__'
let proxyServer = keys[0]
if (RequestCounter && keys.length > 1) {
const count = RequestCounter.getOrCreate(cacheKey, keys)
if (count.value == null) {
count.doRank()
}
if (count.value == null) {
log.error('`count.value` is null, the count:', count)
} else {
count.doCount(count.value)
proxyServer = count.value
context.requestCount = {
key: cacheKey,
value: count.value,
count,
}
}
}
const domain = proxyServer
const port = server[domain].port
const path = server[domain].path
const password = server[domain].password
const proxyTarget = `${domain}/${path}/${hostname}${req.url}`
// const backup = interceptOpt.backup
const proxy = proxyTarget.indexOf('http:') === 0 || proxyTarget.indexOf('https:') === 0 ? proxyTarget : (`${rOptions.protocol}//${proxyTarget}`)
// eslint-disable-next-line node/no-deprecated-api
const URL = url.parse(proxy)
rOptions.origional = lodash.cloneDeep(rOptions) // 备份原始请求参数
delete rOptions.origional.agent
delete rOptions.origional.headers
rOptions.protocol = URL.protocol
rOptions.hostname = URL.host
rOptions.host = URL.host
rOptions.headers.host = URL.host
if (password) {
rOptions.headers.dspassword = password
}
rOptions.path = URL.path
if (URL.port == null) {
rOptions.port = port || (rOptions.protocol === 'https:' ? 443 : 80)
}
log.info('OverWall:', rOptions.hostname, '➜', proxyTarget)
if (context.requestCount) {
log.debug('OverWall choice:', JSON.stringify(context.requestCount))
}
res.setHeader('DS-Overwall', matchedResult)
return true
},
}
}
module.exports = {
getTmpPacFilePath,
downloadPacAsync,
createOverwallMiddleware,
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/middleware/source/pac.js
================================================
const fs = require('node:fs')
const path = require('node:path')
const log = require('../../../../utils/util.log.server')
function createPacClient (pacFilePath) {
const __PROXY__ = 'PROXY 127.0.0.1:1080;'
function readFile (location) {
try {
log.info('pac root dir:', path.resolve('./'))
log.info('pac location:', location)
const filePath = path.resolve(location)
log.info('read pac path:', filePath)
return fs.readFileSync(location).toString()
} catch (e) {
log.error('读取pac失败:', e)
return ''
}
}
const getRules = function (pacFilePath) {
let text = readFile(pacFilePath)
if (!text.includes('!---------------------EOF')) {
text = Buffer.from(text, 'base64').toString()
}
const rules = []
const arr = text.split('\n')
for (const line of arr) {
const row = line.trim()
if (row === '' || row.indexOf('!') === 0 || row.indexOf('[') === 0) {
continue
}
rules.push(row)
}
return rules
}
const __RULES__ = getRules(pacFilePath)
/* eslint-disable */
// Was generated by gfwlist2pac in precise mode
// https://github.com/clowwindy/gfwlist2pac
// 2019-10-06: More 'javascript' way to interaction with main program
// 2019-02-08: Updated to support shadowsocks-windows user rules.
const proxy = __PROXY__
const rules = []
// convert to abp grammar
for (let i = 0; i < __RULES__.length; i++) {
let s = __RULES__[i]
if (s.substring(0, 2) === "||") s += "^"
rules.push(s)
}
/*
* This file is part of Adblock Plus ,
* Copyright (C) 2006-2014 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see .
*/
function createDict () {
const result = {}
result.__proto__ = null
return result
}
function getOwnPropertyDescriptor (obj, key) {
if (obj.hasOwnProperty(key)) {
return obj[key]
}
return null
}
function extend (subClass, superClass, definition) {
if (Object.__proto__) {
definition.__proto__ = superClass.prototype
subClass.prototype = definition
} else {
const tmpClass = function () {}
tmpClass.prototype = superClass.prototype
subClass.prototype = new tmpClass()
subClass.prototype.constructor = superClass
for (const key in definition) {
if (definition.hasOwnProperty(key)) {
subClass.prototype[key] = definition[key]
}
}
}
}
function Filter (text) {
this.text = text
this.subscriptions = []
}
Filter.prototype = {
text: null,
subscriptions: null,
toString: function () {
return this.text
}
}
Filter.knownFilters = createDict()
Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/
Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/
Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/
Filter.fromText = function (text) {
if (text in Filter.knownFilters) {
return Filter.knownFilters[text]
}
let ret
if (text.charAt(0) === "!") {
ret = new CommentFilter(text)
} else {
ret = RegExpFilter.fromText(text)
}
Filter.knownFilters[ret.text] = ret
return ret
}
function InvalidFilter (text, reason) {
Filter.call(this, text)
this.reason = reason
}
extend(InvalidFilter, Filter, {
reason: null
})
function CommentFilter (text) {
Filter.call(this, text)
}
extend(CommentFilter, Filter, {})
function ActiveFilter (text, domains) {
Filter.call(this, text)
this.domainSource = domains
}
extend(ActiveFilter, Filter, {
domainSource: null,
domainSeparator: null,
ignoreTrailingDot: true,
domainSourceIsUpperCase: false,
getDomains: function () {
const prop = getOwnPropertyDescriptor(this, "domains")
if (prop) {
return prop
}
let domains = null
if (this.domainSource) {
let source = this.domainSource
if (!this.domainSourceIsUpperCase) {
source = source.toUpperCase()
}
const list = source.split(this.domainSeparator)
if (list.length === 1 && (list[0]).charAt(0) !== "~") {
domains = createDict()
domains[""] = false
if (this.ignoreTrailingDot) {
list[0] = list[0].replace(/\.+$/, "")
}
domains[list[0]] = true
} else {
let hasIncludes = false
for (let i = 0; i < list.length; i++) {
let domain = list[i]
if (this.ignoreTrailingDot) {
domain = domain.replace(/\.+$/, "")
}
if (domain === "") {
continue
}
let include
if (domain.charAt(0) === "~") {
include = false
domain = domain.substr(1)
} else {
include = true
hasIncludes = true
}
if (!domains) {
domains = createDict()
}
domains[domain] = include
}
domains[""] = !hasIncludes
}
this.domainSource = null
}
return this.domains
},
siteKeys: null,
isActiveOnDomain: function (docDomain, siteKey) {
if (this.getSiteKeys() && (!siteKey || this.getSiteKeys().indexOf(siteKey.toUpperCase()) < 0)) {
return false
}
if (!this.getDomains()) {
return true
}
if (!docDomain) {
return this.getDomains()[""]
}
if (this.ignoreTrailingDot) {
docDomain = docDomain.replace(/\.+$/, "")
}
docDomain = docDomain.toUpperCase()
while (true) {
if (docDomain in this.getDomains()) {
return this.domains[docDomain]
}
const nextDot = docDomain.indexOf(".")
if (nextDot < 0) {
break
}
docDomain = docDomain.substr(nextDot + 1)
}
return this.domains[""]
}/*,
isActiveOnlyOnDomain: function (docDomain) {
if (!docDomain || !this.getDomains() || this.getDomains()[""]) {
return false
}
if (this.ignoreTrailingDot) {
docDomain = docDomain.replace(/\.+$/, "")
}
docDomain = docDomain.toUpperCase()
for (const domain in this.getDomains()) {
if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1)) {
return false
}
}
return true
}*/
})
function RegExpFilter (text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys) {
ActiveFilter.call(this, text, domains, siteKeys)
if (contentType != null) {
this.contentType = contentType
}
if (matchCase) {
this.matchCase = matchCase
}
if (thirdParty != null) {
this.thirdParty = thirdParty
}
if (siteKeys != null) {
this.siteKeySource = siteKeys
}
if (regexpSource.length >= 2 && regexpSource.charAt(0) === "/" && regexpSource.charAt(regexpSource.length - 1) === "/") {
this.regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i")
} else {
this.regexpSource = regexpSource
}
}
extend(RegExpFilter, ActiveFilter, {
domainSourceIsUpperCase: true,
length: 1,
domainSeparator: "|",
regexpSource: null,
getRegexp: function () {
const prop = getOwnPropertyDescriptor(this, "regexp")
if (prop) {
return prop
}
const source = this.regexpSource.replace(/\*+/g, "*").replace(/\^\|$/, "^").replace(/\W/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?").replace(/^\\\|/, "^").replace(/\\\|$/, "$").replace(/^(\.\*)/, "").replace(/(\.\*)$/, "")
const regexp = new RegExp(source, this.matchCase ? "" : "i")
this.regexp = regexp
return regexp
},
contentType: 2147483647,
matchCase: false,
thirdParty: null,
siteKeySource: null,
getSiteKeys: function () {
const prop = getOwnPropertyDescriptor(this, "siteKeys")
if (prop) {
return prop
}
let siteKeys = null
if (this.siteKeySource) {
siteKeys = this.siteKeySource.split("|")
this.siteKeySource = null
}
this.siteKeys = siteKeys
return this.siteKeys
},
matches: function (location, contentType, docDomain, thirdParty, siteKey) {
return !!(this.getRegexp().test(location) && this.isActiveOnDomain(docDomain, siteKey))
}
})
RegExpFilter.prototype["0"] = "#this"
RegExpFilter.fromText = function (text) {
let blocking = true
const origText = text
if (text.indexOf("@@") === 0) {
blocking = false
text = text.substr(2)
}
let contentType = null
let matchCase = null
let domains = null
let siteKeys = null
let thirdParty = null
let collapse = null
let options
const match = text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null
if (match) {
options = match[1].toUpperCase().split(",")
text = match.input.substr(0, match.index)
for (let _loopIndex6 = 0; _loopIndex6 < options.length; ++_loopIndex6) {
let option = options[_loopIndex6]
let value = null
const separatorIndex = option.indexOf("=")
if (separatorIndex >= 0) {
value = option.substr(separatorIndex + 1)
option = option.substr(0, separatorIndex)
}
option = option.replace(/-/, "_")
if (option in RegExpFilter.typeMap) {
if (contentType == null) {
contentType = 0
}
contentType |= RegExpFilter.typeMap[option]
} else if (option.charAt(0) === "~" && option.substr(1) in RegExpFilter.typeMap) {
if (contentType == null) {
contentType = RegExpFilter.prototype.contentType
}
contentType &= ~RegExpFilter.typeMap[option.substr(1)]
} else if (option === "MATCH_CASE") {
matchCase = true
} else if (option === "~MATCH_CASE") {
matchCase = false
} else if (option === "DOMAIN" && typeof value != "undefined") {
domains = value
} else if (option === "THIRD_PARTY") {
thirdParty = true
} else if (option === "~THIRD_PARTY") {
thirdParty = false
} else if (option === "COLLAPSE") {
collapse = true
} else if (option === "~COLLAPSE") {
collapse = false
} else if (option === "SITEKEY" && typeof value != "undefined") {
siteKeys = value
} else {
return new InvalidFilter(origText, "Unknown option " + option.toLowerCase())
}
}
}
if (!blocking && (contentType == null || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text)) {
if (contentType == null) {
contentType = RegExpFilter.prototype.contentType
}
contentType &= ~RegExpFilter.typeMap.DOCUMENT
}
try {
if (blocking) {
return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, siteKeys, collapse)
} else {
return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, siteKeys)
}
} catch (e) {
return new InvalidFilter(origText, e)
}
}
RegExpFilter.typeMap = {
OTHER: 1,
SCRIPT: 2,
IMAGE: 4,
STYLESHEET: 8,
OBJECT: 16,
SUBDOCUMENT: 32,
DOCUMENT: 64,
XBL: 1,
PING: 1,
XMLHTTPREQUEST: 2048,
OBJECT_SUBREQUEST: 4096,
DTD: 1,
MEDIA: 16384,
FONT: 32768,
BACKGROUND: 4,
POPUP: 268435456,
ELEMHIDE: 1073741824
}
RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.POPUP)
function BlockingFilter (text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys, collapse) {
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys)
this.collapse = collapse
}
extend(BlockingFilter, RegExpFilter, {
collapse: null
})
function WhitelistFilter (text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys) {
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys)
}
extend(WhitelistFilter, RegExpFilter, {})
function Matcher () {
this.clear()
}
Matcher.prototype = {
filterByKeyword: null,
keywordByFilter: null,
clear: function () {
this.filterByKeyword = createDict()
this.keywordByFilter = createDict()
},
add: function (filter) {
if (filter.text in this.keywordByFilter) {
return
}
const keyword = this.findKeyword(filter)
const oldEntry = this.filterByKeyword[keyword]
if (typeof oldEntry == "undefined") {
this.filterByKeyword[keyword] = filter
} else if (oldEntry.length === 1) {
this.filterByKeyword[keyword] = [oldEntry, filter]
} else {
oldEntry.push(filter)
}
this.keywordByFilter[filter.text] = keyword
},
remove: function (filter) {
if (!(filter.text in this.keywordByFilter)) {
return
}
const keyword = this.keywordByFilter[filter.text]
const list = this.filterByKeyword[keyword]
if (list.length <= 1) {
delete this.filterByKeyword[keyword]
} else {
const index = list.indexOf(filter)
if (index >= 0) {
list.splice(index, 1)
if (list.length === 1) {
this.filterByKeyword[keyword] = list[0]
}
}
}
delete this.keywordByFilter[filter.text]
},
findKeyword: function (filter) {
let result = ""
let text = filter.text
if (Filter.regexpRegExp.test(text)) {
return result
}
const match = Filter.optionsRegExp.exec(text)
if (match) {
text = match.input.substr(0, match.index)
}
if (text.substr(0, 2) === "@@") {
text = text.substr(2)
}
const candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g)
if (!candidates) {
return result
}
const hash = this.filterByKeyword
let resultCount = 16777215
let resultLength = 0
for (let i = 0, l = candidates.length; i < l; i++) {
const candidate = candidates[i].substr(1)
const count = candidate in hash ? hash[candidate].length : 0
if (count < resultCount || count === resultCount && candidate.length > resultLength) {
result = candidate
resultCount = count
resultLength = candidate.length
}
}
return result
},
hasFilter: function (filter) {
return filter.text in this.keywordByFilter
},
getKeywordForFilter: function (filter) {
if (filter.text in this.keywordByFilter) {
return this.keywordByFilter[filter.text]
} else {
return null
}
},
_checkEntryMatch: function (keyword, location, contentType, docDomain, thirdParty, siteKey) {
const list = this.filterByKeyword[keyword]
for (let i = 0; i < list.length; i++) {
let filter = list[i]
if (filter === "#this") {
filter = list
}
if (filter.matches(location, contentType, docDomain, thirdParty, siteKey)) {
return filter
}
}
return null
}/*,
matchesAny: function (location, contentType, docDomain, thirdParty, siteKey) {
let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g)
if (candidates === null) {
candidates = []
}
candidates.push("")
for (let i = 0, l = candidates.length; i < l; i++) {
const substr = candidates[i]
if (substr in this.filterByKeyword) {
const result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, siteKey)
if (result) {
return result
}
}
}
return null
}*/
}
function CombinedMatcher () {
this.blacklist = new Matcher()
this.whitelist = new Matcher()
this.resultCache = createDict()
}
CombinedMatcher.maxCacheEntries = 1000
CombinedMatcher.prototype = {
blacklist: null,
whitelist: null,
resultCache: null,
cacheEntries: 0,
clear: function () {
this.blacklist.clear()
this.whitelist.clear()
this.resultCache = createDict()
this.cacheEntries = 0
},
add: function (filter) {
if (filter instanceof WhitelistFilter) {
this.whitelist.add(filter)
} else {
this.blacklist.add(filter)
}
if (this.cacheEntries > 0) {
this.resultCache = createDict()
this.cacheEntries = 0
}
},
remove: function (filter) {
if (filter instanceof WhitelistFilter) {
this.whitelist.remove(filter)
} else {
this.blacklist.remove(filter)
}
if (this.cacheEntries > 0) {
this.resultCache = createDict()
this.cacheEntries = 0
}
},
findKeyword: function (filter) {
if (filter instanceof WhitelistFilter) {
return this.whitelist.findKeyword(filter)
} else {
return this.blacklist.findKeyword(filter)
}
},
hasFilter: function (filter) {
if (filter instanceof WhitelistFilter) {
return this.whitelist.hasFilter(filter)
} else {
return this.blacklist.hasFilter(filter)
}
},
getKeywordForFilter: function (filter) {
if (filter instanceof WhitelistFilter) {
return this.whitelist.getKeywordForFilter(filter)
} else {
return this.blacklist.getKeywordForFilter(filter)
}
},
/*isSlowFilter: function (filter) {
const matcher = filter instanceof WhitelistFilter ? this.whitelist : this.blacklist
if (matcher.hasFilter(filter)) {
return !matcher.getKeywordForFilter(filter)
} else {
return !matcher.findKeyword(filter)
}
},*/
matchesAnyInternal: function (location, contentType, docDomain, thirdParty, siteKey) {
let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g)
if (candidates === null) {
candidates = []
}
candidates.push("")
let blacklistHit = null
for (let i = 0, l = candidates.length; i < l; i++) {
const substr = candidates[i]
if (substr in this.whitelist.filterByKeyword) {
const result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, siteKey)
if (result) {
return result
}
}
if (substr in this.blacklist.filterByKeyword && blacklistHit === null) {
blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, siteKey)
}
}
return blacklistHit
},
matchesAny: function (location, docDomain) {
const key = location + " " + docDomain + " "
if (key in this.resultCache) {
return this.resultCache[key]
}
const result = this.matchesAnyInternal(location, 0, docDomain, null, null)
if (this.cacheEntries >= CombinedMatcher.maxCacheEntries) {
this.resultCache = createDict()
this.cacheEntries = 0
}
this.resultCache[key] = result
this.cacheEntries++
return result
}
}
const userrulesMatcher = new CombinedMatcher()
const defaultMatcher = new CombinedMatcher()
const direct = 'DIRECT;'
for (let i = 0; i < rules.length; i++) {
defaultMatcher.add(Filter.fromText(rules[i]))
}
function FindProxyForURL (url, host) {
let matchedResult = userrulesMatcher.matchesAny(url, host)
if (matchedResult instanceof BlockingFilter) {
return proxy
} else if (matchedResult instanceof WhitelistFilter) {
return direct
}
// Hack for Geosite, it provides a whitelist...
matchedResult = defaultMatcher.matchesAny(url, host)
if (matchedResult instanceof BlockingFilter) {
return proxy
} else if (matchedResult instanceof WhitelistFilter) {
return direct
}
return direct
}
return {
FindProxyForURL,
proxyUrl: __PROXY__
}
}
module.exports = {
createPacClient
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js
================================================
const net = require('node:net')
const url = require('node:url')
const jsonApi = require('../../../json')
const log = require('../../../utils/util.log.server')
const DnsUtil = require('../../dns')
const dnsLookup = require('./dnsLookup')
const localIP = '127.0.0.1'
function isSslConnect (sslConnectInterceptors, req, cltSocket, head) {
for (const intercept of sslConnectInterceptors) {
const ret = intercept(req, cltSocket, head)
log.debug('当前拦截器返回结果:', ret, `, url: ${req.url}, intercept:`, intercept)
if (ret == null) {
continue
}
return !(ret === false || ret === 'false')
}
return false
}
// create connectHandler function
module.exports = function createConnectHandler (sslConnectInterceptor, middlewares, fakeServerCenter, dnsConfig, compatibleConfig) {
// return
const sslConnectInterceptors = []
sslConnectInterceptors.push(sslConnectInterceptor)
for (const middleware of middlewares) {
if (middleware.sslConnectInterceptor) {
sslConnectInterceptors.push(middleware.sslConnectInterceptor)
}
}
return function connectHandler (req, cltSocket, head, ssl) {
// eslint-disable-next-line node/no-deprecated-api
let { hostname, port } = url.parse(`${ssl ? 'https' : 'http'}://${req.url}`)
port = Number.parseInt(port)
if (isSslConnect(sslConnectInterceptors, req, cltSocket, head)) {
// 需要拦截,代替目标服务器,让客户端连接DS在本地启动的代理服务
fakeServerCenter.getServerPromise(hostname, port, ssl, compatibleConfig).then((serverObj) => {
log.info(`----- fakeServer connect: ${localIP}:${serverObj.port} ➜ ${req.url} -----`)
connect(req, cltSocket, head, localIP, serverObj.port, null, false, hostname)
}, (e) => {
log.error(`----- fakeServer getServerPromise error: ${hostname}:${port}, error:`, e)
}).catch((e) => {
log.error(`----- fakeServer getServerPromise error: ${hostname}:${port}, error:`, e)
})
} else {
log.info(`不拦截请求,直连目标服务器: ${hostname}:${port}, headers:`, jsonApi.stringify2(req.headers))
connect(req, cltSocket, head, hostname, port, dnsConfig, true)
}
}
}
function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDirect = false, target = null) {
// tunneling https
// log.info('connect:', hostname, port)
const start = Date.now()
const isDnsIntercept = {}
const hostport = `${hostname}:${port}`
// 用于记录日志
const connectInfo = isDirect ? hostport : `fakeServer: ${hostport}, target: ${target}`
try {
// 客户端的连接事件监听
cltSocket.on('timeout', (e) => {
log.error(`cltSocket timeout: ${connectInfo}, errorMsg: ${e.message}`)
})
cltSocket.on('error', (e) => {
log.error(`cltSocket error: ${connectInfo}, errorMsg: ${e.message}`)
})
// 开发过程中,如有需要可以将此参数临时改为true,打印所有事件的日志
const printDebugLog = process.env.NODE_ENV === 'development' && false
if (printDebugLog) {
cltSocket.on('close', (hadError) => {
log.debug('【cltSocket close】', hadError)
})
cltSocket.on('connect', () => {
log.debug('【cltSocket connect】')
})
cltSocket.on('connectionAttempt', (ip, port, family) => {
log.debug(`【cltSocket connectionAttempt】${ip}:${port}: ${connectInfo}, family:`, family)
})
cltSocket.on('connectionAttemptFailed', (ip, port, family) => {
log.debug(`【cltSocket connectionAttemptFailed】${ip}:${port}: ${connectInfo}, family:`, family)
})
cltSocket.on('connectionAttemptTimeout', (ip, port, family) => {
log.debug(`【cltSocket connectionAttemptTimeout】${ip}:${port}: ${connectInfo}, family:`, family)
})
cltSocket.on('data', (_data) => {
log.debug(`【cltSocket data】${connectInfo}`)
})
cltSocket.on('drain', () => {
log.debug(`【cltSocket drain】${connectInfo}`)
})
cltSocket.on('end', () => {
log.debug(`【cltSocket end】${connectInfo}`)
})
// cltSocket.on('lookup', (err, address, family, host) => {
// })
cltSocket.on('ready', () => {
log.debug(`【cltSocket ready】${connectInfo}`)
})
}
// ---------------------------------------------------------------------------------------------------
const options = {
port,
host: hostname,
connectTimeout: 10000,
}
if (dnsConfig && dnsConfig.dnsMap) {
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
if (dns) {
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, port, isDnsIntercept)
}
}
// 代理连接事件监听
const proxySocket = net.connect(options, () => {
if (!isDirect) {
log.info(`Proxy connect start: ${hostport}`)
} else {
log.debug('Direct connect start:', hostport)
}
cltSocket.write('HTTP/1.1 200 Connection Established\r\n'
+ 'Proxy-agent: dev-sidecar\r\n'
+ '\r\n')
proxySocket.write(head)
proxySocket.pipe(cltSocket)
cltSocket.pipe(proxySocket)
})
proxySocket.on('timeout', () => {
const cost = Date.now() - start
const errorMsg = `${isDirect ? '直连' : '代理连接'}超时: ${hostport}, cost: ${cost} ms`
log.error(errorMsg)
cltSocket.destroy()
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
const { dns, ip, hostname } = isDnsIntercept
dns.count(hostname, ip, true)
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.dnsName}`)
}
})
proxySocket.on('error', (e) => {
// 连接失败,可能被GFW拦截,或者服务端拥挤
const cost = Date.now() - start
const errorMsg = `${isDirect ? '直连' : '代理连接'}失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}`
log.error(`${errorMsg}\r\n`, e)
cltSocket.destroy()
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
const { dns, ip, hostname } = isDnsIntercept
dns.count(hostname, ip, true)
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.dnsName}`)
}
})
if (printDebugLog) {
proxySocket.on('close', (hadError) => {
log.debug('【proxySocket close】', hadError)
})
proxySocket.on('connect', () => {
log.debug('【proxySocket connect】')
})
proxySocket.on('connectionAttempt', (ip, port, family) => {
log.debug(`【proxySocket connectionAttempt】${ip}:${port}, family:`, family)
})
proxySocket.on('connectionAttemptFailed', (ip, port, family) => {
log.debug(`【proxySocket connectionAttemptFailed】${ip}:${port}, family:`, family)
})
proxySocket.on('connectionAttemptTimeout', (ip, port, family) => {
log.debug(`【proxySocket connectionAttemptTimeout】${ip}:${port}, family:`, family)
})
proxySocket.on('data', (_data) => {
log.debug('【proxySocket data】')
})
proxySocket.on('drain', () => {
log.debug('【proxySocket drain】')
})
proxySocket.on('end', () => {
log.debug('【proxySocket end】')
})
// proxySocket.on('lookup', (err, address, family, host) => {
// })
proxySocket.on('ready', () => {
log.debug('【proxySocket ready】')
})
}
return proxySocket
} catch (e) {
log.error(`${isDirect ? '直连' : '代理连接'}错误: ${hostport}, error:`, e)
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/mitmproxy/createFakeServerCenter.js
================================================
const fs = require('node:fs')
const forge = require('node-forge')
const log = require('../../../utils/util.log.server')
const FakeServersCenter = require('../tls/FakeServersCenter')
module.exports = function createFakeServerCenter ({
maxLength,
caCertPath,
caKeyPath,
requestHandler,
upgradeHandler,
getCertSocketTimeout,
}) {
let caCert
let caKey
try {
fs.accessSync(caCertPath, fs.F_OK)
fs.accessSync(caKeyPath, fs.F_OK)
const caCertPem = fs.readFileSync(caCertPath)
const caKeyPem = fs.readFileSync(caKeyPath)
caCert = forge.pki.certificateFromPem(caCertPem)
caKey = forge.pki.privateKeyFromPem(caKeyPem)
} catch (e) {
log.error('Can not find `CA certificate` or `CA key`:', e)
process.exit(1)
}
return new FakeServersCenter({
caCert,
caKey,
maxLength,
requestHandler,
upgradeHandler,
getCertSocketTimeout,
})
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js
================================================
const http = require('node:http')
const https = require('node:https')
const jsonApi = require('../../../json')
const log = require('../../../utils/util.log.server')
const RequestCounter = require('../../choice/RequestCounter')
const commonUtil = require('../common/util')
// const upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i
const DnsUtil = require('../../dns')
const compatible = require('../compatible/compatible')
const InsertScriptMiddleware = require('../middleware/InsertScriptMiddleware')
const dnsLookup = require('./dnsLookup')
const MAX_SLOW_TIME = 8000 // 超过此时间 则认为太慢了
// create requestHandler function
module.exports = function createRequestHandler (createIntercepts, middlewares, externalProxy, dnsConfig, setting, compatibleConfig) {
// return
return function requestHandler (req, res, ssl) {
let proxyReq
const rOptions = commonUtil.getOptionsFromRequest(req, ssl, externalProxy, setting, compatibleConfig)
let url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
if (rOptions.headers.connection === 'close') {
req.socket.setKeepAlive(false)
} else if (rOptions.customSocketId != null) { // for NTLM
req.socket.setKeepAlive(true, 60 * 60 * 1000)
} else {
req.socket.setKeepAlive(true, 30000)
}
const context = {
rOptions,
log,
RequestCounter,
setting,
}
let interceptors = createIntercepts(context)
if (interceptors == null) {
interceptors = []
}
const reqIncpts = interceptors.filter((item) => {
return item.requestIntercept != null
})
const resIncpts = interceptors.filter((item) => {
return item.responseIntercept != null
})
const requestInterceptorPromise = () => {
return new Promise((resolve, reject) => {
const next = () => {
resolve()
}
try {
if (setting.script.enabled) {
reqIncpts.unshift(InsertScriptMiddleware)
}
for (const middleware of middlewares) {
reqIncpts.push(middleware)
}
if (reqIncpts && reqIncpts.length > 0) {
for (const reqIncpt of reqIncpts) {
if (!reqIncpt.requestIntercept) {
continue
}
const goNext = reqIncpt.requestIntercept(context, req, res, ssl, next)
if (goNext) {
if (goNext !== 'no-next') {
next()
}
return
}
}
next()
} else {
next()
}
} catch (e) {
reject(e)
}
})
}
function countSlow (isDnsIntercept, reason) {
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
const { dns, ip, hostname } = isDnsIntercept
dns.count(hostname, ip, true)
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${reason}, dns: ${dns.dnsName}`)
}
const counter = context.requestCount
if (counter != null) {
counter.count.doCount(counter.value, true)
log.error(`记录Proxy请求失败次数,用于切换备选域名! hostname: ${counter.value}, reason: ${reason}, counter.count:`, counter.count)
}
}
const proxyRequestPromise = async () => {
rOptions.host = rOptions.hostname || rOptions.host || 'localhost'
return new Promise((resolve, reject) => {
// use the binded socket for NTLM
if (rOptions.agent && rOptions.customSocketId != null && rOptions.agent.getName) {
const socketName = rOptions.agent.getName(rOptions)
const bindingSocket = rOptions.agent.sockets[socketName]
if (bindingSocket && bindingSocket.length > 0) {
bindingSocket[0].once('free', onFree)
return
}
}
onFree()
function onFree () {
url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
const start = Date.now()
log.info('发起代理请求:', url, (rOptions.servername ? `, sni: ${rOptions.servername}` : ''), ', headers:', jsonApi.stringify2(rOptions.headers))
const isDnsIntercept = {}
if (dnsConfig && dnsConfig.dnsMap) {
let dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname)
if (!dns && rOptions.servername) {
dns = dnsConfig.dnsMap.ForSNI
if (dns) {
log.info(`域名 ${rOptions.hostname} 在dns中未配置,但使用了 sni: ${rOptions.servername}, 必须使用dns,现默认使用 '${dns.dnsName}' DNS.`)
} else {
log.warn(`域名 ${rOptions.hostname} 在dns中未配置,但使用了 sni: ${rOptions.servername},且DNS服务管理中,也未指定SNI默认使用的DNS。`)
}
}
if (dns) {
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, rOptions.port, isDnsIntercept)
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`)
res.setHeader('DS-DNS', dns.dnsName)
} else {
log.info(`域名 ${rOptions.hostname} 在DNS中未配置`)
}
} else {
log.info(`域名 ${rOptions.hostname} DNS配置不存在`)
}
// rOptions.sigalgs = 'RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256'
// rOptions.agent.options.sigalgs = rOptions.sigalgs
// rOptions.ciphers = 'TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA256:ECDHE-RSA-AES256-SHA256:HIGH'
// rOptions.agent.options.ciphers = rOptions.ciphers
// log.debug('rOptions:', rOptions.hostname + rOptions.path, '\r\n', rOptions)
// log.debug('agent:', rOptions.agent)
// log.debug('agent.options:', rOptions.agent.options)
res.setHeader('DS-Proxy-Request', rOptions.hostname)
// 自动兼容程序:2
if (rOptions.agent) {
const compatibleConfig = compatible.getRequestCompatibleConfig(rOptions, rOptions.compatibleConfig)
if (compatibleConfig && compatibleConfig.rejectUnauthorized != null && rOptions.agent.options.rejectUnauthorized !== compatibleConfig.rejectUnauthorized) {
if (compatibleConfig.rejectUnauthorized === false && rOptions.agent.unVerifySslAgent) {
log.info(`【自动兼容程序】${rOptions.hostname}:${rOptions.port}: 设置 'rOptions.agent.options.rejectUnauthorized = ${compatibleConfig.rejectUnauthorized}'`)
rOptions.agent = rOptions.agent.unVerifySslAgent
res.setHeader('DS-Compatible', 'unVerifySsl')
}
}
}
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
const cost = Date.now() - start
if (rOptions.protocol === 'https:') {
log.info(`代理请求返回: 【${proxyRes.statusCode}】${url}, cost: ${cost} ms`)
} else {
log.info(`请求返回: 【${proxyRes.statusCode}】${url}, cost: ${cost} ms`)
}
// log.info('request:', proxyReq, proxyReq.socket)
if (cost > MAX_SLOW_TIME) {
countSlow(isDnsIntercept, `代理请求成功但太慢, cost: ${cost} ms > ${MAX_SLOW_TIME} ms`)
}
resolve(proxyRes)
})
// 代理请求的事件监听
proxyReq.on('timeout', () => {
const cost = Date.now() - start
const errorMsg = `代理请求超时: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
countSlow(isDnsIntercept, `代理请求超时, cost: ${cost} ms`)
proxyReq.end()
proxyReq.destroy()
const error = new Error(errorMsg)
error.status = 408
reject(error)
})
proxyReq.on('error', (e) => {
const cost = Date.now() - start
log.error(`代理请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
countSlow(isDnsIntercept, `代理请求错误: ${e.message}`)
reject(e)
// 自动兼容程序:2
if (e.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
compatible.setRequestRejectUnauthorized(rOptions, false)
}
})
proxyReq.on('aborted', () => {
const cost = Date.now() - start
const errorMsg = `代理请求被取消: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
if (cost > MAX_SLOW_TIME) {
countSlow(isDnsIntercept, `代理请求被取消,且请求太慢, cost: ${cost} ms > ${MAX_SLOW_TIME} ms`)
}
if (res.writableEnded) {
return
}
reject(new Error(errorMsg))
})
// 原始请求的事件监听
req.on('aborted', () => {
const cost = Date.now() - start
const errorMsg = `请求被取消: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
proxyReq.abort()
if (res.writableEnded) {
return
}
reject(new Error(errorMsg))
})
req.on('error', (e, req, res) => {
const cost = Date.now() - start
log.error(`请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
reject(e)
})
req.on('timeout', () => {
const cost = Date.now() - start
const errorMsg = `请求超时: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
reject(new Error(errorMsg))
})
req.pipe(proxyReq)
}
})
}
// workflow control
(async () => {
await requestInterceptorPromise()
if (res.writableEnded) {
// log.info('res is writableEnded, return false')
return false
}
const proxyRes = await proxyRequestPromise()
// proxyRes.on('data', (chunk) => {
// // log.info('BODY: ')
// })
proxyRes.on('error', (error) => {
countSlow(null, `error: ${error.message}`)
log.error(`proxy res error: ${url}, error:`, error)
})
const responseInterceptorPromise = new Promise((resolve, reject) => {
const next = () => {
resolve()
}
for (const middleware of middlewares) {
if (middleware.responseInterceptor) {
middleware.responseInterceptor(req, res, proxyReq, proxyRes, ssl, next)
}
}
if (!setting.script.enabled) {
next()
return
}
try {
if (resIncpts && resIncpts.length > 0) {
let head = ''
let body = ''
for (const resIncpt of resIncpts) {
const append = resIncpt.responseIntercept(context, req, res, proxyReq, proxyRes, ssl, next)
// 判断是否已经关闭
if (res.writableEnded) {
next()
return
}
if (append) {
if (append.head) {
head += append.head
}
if (append.body) {
body += append.body
}
} else if (append === false) {
break // 返回false表示终止拦截器,跳出循环
}
}
InsertScriptMiddleware.responseInterceptor(req, res, proxyReq, proxyRes, ssl, next, {
head,
body,
})
} else {
next()
}
} catch (e) {
reject(e)
}
})
await responseInterceptorPromise
if (!res.headersSent) { // prevent duplicate set headers
Object.keys(proxyRes.headers).forEach((key) => {
if (proxyRes.headers[key] !== undefined) {
// https://github.com/nodejitsu/node-http-proxy/issues/362
if (/^www-authenticate$/i.test(key)) {
if (proxyRes.headers[key]) {
proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',')
}
key = 'www-authenticate'
}
res.setHeader(key, proxyRes.headers[key])
}
})
if (proxyRes.statusCode >= 400) {
countSlow(null, `Status return: ${proxyRes.statusCode}`)
}
res.writeHead(proxyRes.statusCode)
proxyRes.pipe(res)
}
})().catch((e) => {
if (!res.writableEnded) {
try {
const status = e.status || 500
res.writeHead(status, { 'Content-Type': 'text/html;charset=UTF8' })
res.write(`DevSidecar Error:
目标网站请求错误:【${e.code}】 ${e.message}
目标地址:${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`,
)
} catch (e) {
// do nothing
}
try {
res.end()
} catch (e) {
// do nothing
}
// region 忽略部分已经打印过ERROR日志的错误
if (e.message) {
const ignoreErrors = [
'代理请求错误: ',
'代理请求超时: ',
'代理请求被取消: ',
]
for (const ignoreError of ignoreErrors) {
if (e.message.startsWith(ignoreError)) {
return
}
}
}
// endregion
log.error(`Request error: ${url}, error:`, e)
}
})
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/mitmproxy/createUpgradeHandler.js
================================================
const http = require('node:http')
const https = require('node:https')
const log = require('../../../utils/util.log.server')
const util = require('../common/util')
// copy from node-http-proxy. ^_^
// create connectHandler function
module.exports = function createUpgradeHandler (serverSetting) {
// return
return function upgradeHandler (req, cltSocket, head, ssl) {
const clientOptions = util.getOptionsFromRequest(req, ssl, null, serverSetting)
const proxyReq = (ssl ? https : http).request(clientOptions)
proxyReq.on('error', (e) => {
log.error('upgradeHandler error:', e)
})
proxyReq.on('response', (res) => {
// if upgrade event isn't going to happen, close the socket
if (!res.upgrade) {
cltSocket.end()
}
})
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
proxySocket.on('error', (e) => {
log.error('upgrade error:', e)
})
cltSocket.on('error', (e) => {
log.error('upgrade socket error:', e)
proxySocket.end()
})
proxySocket.setTimeout(0)
proxySocket.setNoDelay(true)
proxySocket.setKeepAlive(true, 0)
if (proxyHead && proxyHead.length) {
proxySocket.unshift(proxyHead)
}
cltSocket.write(
`${Object.keys(proxyRes.headers).reduce((head, key) => {
const value = proxyRes.headers[key]
if (!Array.isArray(value)) {
head.push(`${key}: ${value}`)
return head
}
for (let i = 0; i < value.length; i++) {
head.push(`${key}: ${value[i]}`)
}
return head
}, ['HTTP/1.1 101 Switching Protocols']).join('\r\n')}\r\n\r\n`,
)
proxySocket.pipe(cltSocket).pipe(proxySocket)
})
proxyReq.end()
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js
================================================
const defaultDns = require('node:dns')
const log = require('../../../utils/util.log.server')
const speedTest = require('../../speed')
function createIpChecker (tester) {
if (!tester || tester.backupList == null || tester.backupList.length === 0) {
return null
}
return (ip) => {
for (let i = 0; i < tester.backupList.length; i++) {
const item = tester.backupList[i]
if (item.host === ip) {
if (item.time > 0) {
return true // IP测速成功
}
if (item.status === 'failed') {
return false // IP测速失败
}
break
}
}
return true // IP测速未知
}
}
module.exports = {
createLookupFunc (res, dns, action, target, port, isDnsIntercept) {
target = target ? (`, target: ${target}`) : ''
return (hostname, options, callback) => {
const tester = speedTest.getSpeedTester(hostname, port)
if (tester) {
const aliveIpObj = tester.pickFastAliveIpObj()
if (aliveIpObj) {
log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `IpTester: ${aliveIpObj.host} ${aliveIpObj.dns === '预设IP' ? 'PreSet' : aliveIpObj.dns}`)
}
callback(null, aliveIpObj.host, 4)
return
} else {
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
}
}
const ipChecker = createIpChecker(tester)
dns.lookup(hostname, ipChecker).then((ip) => {
if (isDnsIntercept) {
isDnsIntercept.dns = dns
isDnsIntercept.hostname = hostname
isDnsIntercept.ip = ip
}
if (ip !== hostname) {
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
}
callback(null, ip, 4)
} else {
// 使用默认dns
log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
defaultDns.lookup(hostname, options, callback)
}
})
}
},
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/mitmproxy/index.js
================================================
const http = require('node:http')
const log = require('../../../utils/util.log.server')
const speedTest = require('../../speed/index.js')
const config = require('../common/config')
const tlsUtils = require('../tls/tlsUtils')
const createConnectHandler = require('./createConnectHandler')
const createFakeServerCenter = require('./createFakeServerCenter')
const createRequestHandler = require('./createRequestHandler')
const createUpgradeHandler = require('./createUpgradeHandler')
module.exports = {
createProxy ({
host = config.defaultHost,
port = config.defaultPort,
maxLength = config.defaultMaxLength,
caCertPath,
caKeyPath,
sslConnectInterceptor,
createIntercepts,
getCertSocketTimeout = 1000,
middlewares = [],
externalProxy,
dnsConfig,
setting,
compatibleConfig,
}, callback) {
// Don't reject unauthorized
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
log.info(`CA Cert read in: ${caCertPath}`)
log.info(`CA private key read in: ${caKeyPath}`)
if (!caCertPath) {
caCertPath = config.getDefaultCACertPath()
}
if (!caKeyPath) {
caKeyPath = config.getDefaultCAKeyPath()
}
const rs = this.createCA({ caCertPath, caKeyPath })
if (rs.create) {
log.info(`CA Cert saved in: ${caCertPath}`)
log.info(`CA private key saved in: ${caKeyPath}`)
}
port = ~~port
const speedTestConfig = dnsConfig.speedTest
const dnsMap = dnsConfig.dnsMap
if (speedTestConfig) {
const dnsProviders = speedTestConfig.dnsProviders
const map = {}
for (const dnsProvider of dnsProviders) {
if (dnsMap[dnsProvider]) {
map[dnsProvider] = dnsMap[dnsProvider]
}
}
speedTest.initSpeedTest({ ...speedTestConfig, dnsMap: map })
}
const requestHandler = createRequestHandler(
createIntercepts,
middlewares,
externalProxy,
dnsConfig,
setting,
compatibleConfig,
)
const upgradeHandler = createUpgradeHandler(setting)
const fakeServersCenter = createFakeServerCenter({
maxLength,
caCertPath,
caKeyPath,
requestHandler,
upgradeHandler,
getCertSocketTimeout,
})
const connectHandler = createConnectHandler(
sslConnectInterceptor,
middlewares,
fakeServersCenter,
dnsConfig,
compatibleConfig,
)
// 创建监听方法,用于监听 http 和 https 两个端口
const printDebugLog = process.env.NODE_ENV === 'development' && false // 开发过程中,如有需要可以将此参数临时改为true,打印所有事件的日志
const serverListen = (server, ssl, port, host) => {
server.listen(port, host, () => {
log.info(`dev-sidecar启动 ${ssl ? 'https' : 'http'} 端口: ${host}:${port}`)
server.on('request', (req, res) => {
if (printDebugLog) {
log.debug(`【server request, ssl: ${ssl}】\r\n----- req -----\r\n`, req, '\r\n----- res -----\r\n', res)
}
requestHandler(req, res, ssl)
})
// tunneling for https
server.on('connect', (req, cltSocket, head) => {
if (printDebugLog) {
log.debug(`【server connect, ssl: ${ssl}】\r\n----- req -----\r\n`, req, '\r\n----- cltSocket -----\r\n', cltSocket, '\r\n----- head -----\r\n', head)
}
connectHandler(req, cltSocket, head, ssl)
})
// TODO: handler WebSocket
server.on('upgrade', (req, cltSocket, head) => {
if (printDebugLog) {
log.debug(`【server upgrade, ssl: ${ssl}】\r\n----- req -----\r\n`, req)
} else {
log.info(`【server upgrade, ssl: ${ssl}】`, req.url)
}
upgradeHandler(req, cltSocket, head, ssl)
})
server.on('error', (err) => {
log.error(`【server error, ssl: ${ssl}】\r\n----- error -----\r\n`, err)
})
server.on('clientError', (err, cltSocket) => {
// log.error(`【server clientError, ssl: ${ssl}】\r\n----- error -----\r\n`, err, '\r\n----- cltSocket -----\r\n', cltSocket)
log.error(`【server clientError, ssl: ${ssl}】socket.localPort = ${cltSocket.localPort}\r\n`, err)
cltSocket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
// 其他事件:仅记录debug日志
if (printDebugLog) {
server.on('close', () => {
log.debug(`【server close, ssl: ${ssl}】no arguments...`)
})
server.on('connection', (cltSocket) => {
log.debug(`【server connection, ssl: ${ssl}】\r\n----- cltSocket -----\r\n`, cltSocket)
})
server.on('listening', () => {
log.debug(`【server listening, ssl: ${ssl}】no arguments...`)
})
server.on('checkContinue', (req, res) => {
log.debug(`【server checkContinue, ssl: ${ssl}】\r\n----- req -----\r\n`, req, '\r\n----- res -----\r\n', res)
})
server.on('checkExpectation', (req, res) => {
log.debug(`【server checkExpectation, ssl: ${ssl}】\r\n----- req -----\r\n`, req, '\r\n----- res -----\r\n', res)
})
server.on('dropRequest', (req, cltSocket) => {
log.debug(`【server checkExpectation, ssl: ${ssl}】\r\n----- req -----\r\n`, req, '\r\n----- cltSocket -----\r\n', cltSocket)
})
}
if (callback) {
callback(server, port, host, ssl)
}
})
}
const httpsServer = new http.Server()
const httpServer = new http.Server()
// `http端口` 比 `https端口` 要小1
const httpsPort = port
const httpPort = port - 1
serverListen(httpsServer, true, httpsPort, host)
serverListen(httpServer, false, httpPort, host)
return [httpsServer, httpServer]
},
createCA (caPaths) {
return tlsUtils.initCA(caPaths)
},
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/tls/CertAndKeyContainer.js
================================================
const tlsUtils = require('./tlsUtils')
// const https = require('https')
const log = require('../../../utils/util.log.server')
module.exports = class CertAndKeyContainer {
constructor ({
maxLength = 1000,
// getCertSocketTimeout = 2 * 1000,
caCert,
caKey,
}) {
this.queue = []
this.maxLength = maxLength
// this.getCertSocketTimeout = getCertSocketTimeout
this.caCert = caCert
this.caKey = caKey
}
addCertPromise (certPromiseObj) {
if (this.queue.length >= this.maxLength) {
const delCertObj = this.queue.shift()
log.info(`超过最大证书数量${this.maxLength},删除旧证书。delCertObj:`, delCertObj)
}
this.queue.push(certPromiseObj)
return certPromiseObj
}
getCertPromise (hostname, port, dnsName, mappingHostNames) {
for (let i = 0; i < this.queue.length; i++) {
const _certPromiseObj = this.queue[i]
const mappingHostNames = _certPromiseObj.mappingHostNames
for (let j = 0; j < mappingHostNames.length; j++) {
const DNSName = mappingHostNames[j]
if (DNSName === dnsName || tlsUtils.isMappingHostName(DNSName, hostname)) {
this.reRankCert(i)
log.info(`Load fakeCertPromise from cache, hostname: ${hostname}:${port}, certPromiseObj: {"mappingHostNames":${JSON.stringify(_certPromiseObj.mappingHostNames)}}`)
return _certPromiseObj.promise
}
}
}
const certPromiseObj = {
mappingHostNames,
}
const promise = new Promise((resolve, _reject) => {
log.info(`【CreateFakeCertificate】dnsName: ${dnsName}, hostname: ${hostname}:${port}`)
const certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, dnsName, mappingHostNames)
resolve(certObj)
})
certPromiseObj.promise = promise
this.addCertPromise(certPromiseObj)
return promise
}
reRankCert (index) {
// index ==> queue foot
this.queue.push((this.queue.splice(index, 1))[0])
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/tls/FakeServersCenter.js
================================================
const http = require('node:http')
const https = require('node:https')
const tls = require('node:tls')
const forge = require('node-forge')
const CertAndKeyContainer = require('./CertAndKeyContainer')
const tlsUtils = require('./tlsUtils')
const log = require('../../../utils/util.log.server')
const compatible = require('../compatible/compatible')
const pki = forge.pki
// 获取DNS名称
function getDnsName (hostname) {
if (!hostname.includes('.')) {
return hostname // 可能是IPv6地址,直接返回
}
// 判断是否为IP
if (hostname.match(/\b(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\b){3}/g)) {
return hostname // 为IP,直接返回
}
// 判断是否是一级域名
if (hostname.indexOf('.') === hostname.lastIndexOf('.')) {
return `*.${hostname}`
}
// 获取域名
return `*${hostname.substring(hostname.indexOf('.'))}`
}
module.exports = class FakeServersCenter {
constructor ({ maxLength = 256, requestHandler, upgradeHandler, caCert, caKey, getCertSocketTimeout }) {
this.queue = []
this.maxLength = maxLength
this.requestHandler = requestHandler
this.upgradeHandler = upgradeHandler
this.certAndKeyContainer = new CertAndKeyContainer({
getCertSocketTimeout,
caCert,
caKey,
})
}
addServerPromise (serverPromiseObj) {
if (this.queue.length >= this.maxLength) {
const delServerObj = this.queue.shift()
try {
log.info(`超过最大服务数量${this.maxLength},删除旧服务。delServerObj:`, delServerObj)
delServerObj.serverObj.server.close()
} catch (e) {
log.error('`delServerObj.serverObj.server.close()` error:', e)
}
}
this.queue.push(serverPromiseObj)
return serverPromiseObj
}
getServerPromise (hostname, port, ssl, manualCompatibleConfig) {
if (port === 443 || port === 80) {
ssl = port === 443
} else if (ssl) {
// 自动兼容程序:1
const compatibleConfig = compatible.getConnectCompatibleConfig(hostname, port, manualCompatibleConfig)
if (compatibleConfig && compatibleConfig.ssl != null) {
ssl = compatibleConfig.ssl
}
}
log.info(`getServerPromise, hostname: ${hostname}:${port}, ssl: ${ssl}, protocol: ${ssl ? 'https' : 'http'}`)
for (let i = 0; i < this.queue.length; i++) {
const serverPromiseObj = this.queue[i]
if (serverPromiseObj.port === port && serverPromiseObj.ssl === ssl) {
const mappingHostNames = serverPromiseObj.mappingHostNames
for (let j = 0; j < mappingHostNames.length; j++) {
const DNSName = mappingHostNames[j]
if (tlsUtils.isMappingHostName(DNSName, hostname)) {
this.reRankServer(i)
log.info(`Load fakeServerPromise from cache, hostname: ${hostname}:${port}, ssl: ${ssl}, serverPromiseObj: {"ssl":${serverPromiseObj.ssl},"port":${serverPromiseObj.port},"mappingHostNames":${JSON.stringify(serverPromiseObj.mappingHostNames)}}`)
return serverPromiseObj.promise
}
}
}
}
const dnsName = getDnsName(hostname)
const mappingHostNames = [dnsName]
if (dnsName.startsWith('*.')) {
mappingHostNames.push(dnsName.replace('*.', ''))
}
const serverPromiseObj = {
port,
ssl,
mappingHostNames,
}
const promise = new Promise((resolve, _reject) => {
(async () => {
let fakeServer
let cert
let key
log.info(`【CreateFakeServer】hostname: ${hostname}:${port}, ssl: ${ssl}, protocol: ${ssl ? 'https' : 'http'}`)
if (ssl) {
const certObj = await this.certAndKeyContainer.getCertPromise(hostname, port, dnsName, mappingHostNames)
cert = certObj.cert
key = certObj.key
const certPem = pki.certificateToPem(cert)
const keyPem = pki.privateKeyToPem(key)
fakeServer = new https.Server({
key: keyPem,
cert: certPem,
SNICallback: (hostname, done) => {
(async () => {
log.info(`fakeServer SNICallback: ${hostname}:${port}`)
done(null, tls.createSecureContext({
key: pki.privateKeyToPem(certObj.key),
cert: pki.certificateToPem(certObj.cert),
}))
})()
},
})
} else {
fakeServer = new http.Server()
}
const serverObj = {
cert,
key,
server: fakeServer,
port: 0, // if port === 0 ,should listen server's `listening` event.
}
serverPromiseObj.serverObj = serverObj
const printDebugLog = process.env.NODE_ENV === 'development' && false // 开发过程中,如有需要可以将此参数临时改为true,打印所有事件的日志
fakeServer.listen(0, () => {
const address = fakeServer.address()
serverObj.port = address.port
})
fakeServer.on('request', (req, res) => {
if (printDebugLog) {
log.debug(`【fakeServer request - ${hostname}:${port}】\r\n----- req -----\r\n`, req, '\r\n----- res -----\r\n', res)
}
this.requestHandler(req, res, ssl)
})
fakeServer.on('listening', () => {
if (printDebugLog) {
log.debug(`【fakeServer listening - ${hostname}:${port}】no arguments...`)
}
resolve(serverObj)
})
fakeServer.on('upgrade', (req, socket, head) => {
if (printDebugLog) {
log.debug(`【fakeServer upgrade - ${hostname}:${port}】\r\n----- req -----\r\n`, req, '\r\n----- socket -----\r\n', socket, '\r\n----- head -----\r\n', head)
} else {
log.info(`【fakeServer upgrade - ${hostname}:${port}】`, req.url)
}
this.upgradeHandler(req, socket, head, ssl)
})
// 三个 error 事件
fakeServer.on('error', (e) => {
log.error(`【fakeServer error - ${hostname}:${port}】\r\n----- error -----\r\n`, e)
})
fakeServer.on('clientError', (err, _socket) => {
// log.error(`【fakeServer clientError - ${hostname}:${port}】\r\n----- error -----\r\n`, err, '\r\n----- socket -----\r\n', socket)
log.error(`【fakeServer clientError - ${hostname}:${port}】\r\n`, err)
// 自动兼容程序:1
if (port !== 443 && port !== 80) {
if (ssl === true && err.code.indexOf('ERR_SSL_') === 0) {
compatible.setConnectSsl(hostname, port, false)
log.error(`自动兼容程序:SSL异常,现设置为禁用ssl: ${hostname}:${port}, ssl = false`)
} else if (ssl === false && err.code === 'HPE_INVALID_METHOD') {
compatible.setConnectSsl(hostname, port, true)
log.error(`自动兼容程序:${err.code},现设置为启用ssl: ${hostname}:${port}, ssl = true`)
}
}
})
if (ssl) {
fakeServer.on('tlsClientError', (err, _tlsSocket) => {
if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
return // 在tlsClientError事件中,以上异常不记录日志
}
// log.error(`【fakeServer tlsClientError - ${hostname}:${port}】\r\n----- error -----\r\n`, err, '\r\n----- tlsSocket -----\r\n', tlsSocket)
log.error(`【fakeServer tlsClientError - ${hostname}:${port}】\r\n`, err)
})
}
// 其他监听事件,只打印debug日志
if (printDebugLog) {
if (ssl) {
fakeServer.on('keylog', (line, tlsSocket) => {
log.debug(`【fakeServer keylog - ${hostname}:${port}】\r\n----- line -----\r\n`, line, '\r\n----- tlsSocket -----\r\n', tlsSocket)
})
// fakeServer.on('newSession', (sessionId, sessionData, callback) => {
// log.debug(`【fakeServer newSession - ${hostname}:${port}】\r\n----- sessionId -----\r\n`, sessionId, '\r\n----- sessionData -----\r\n', sessionData, '\r\n----- callback -----\r\n', callback)
// })
// fakeServer.on('OCSPRequest', (certificate, issuer, callback) => {
// log.debug(`【fakeServer OCSPRequest - ${hostname}:${port}】\r\n----- certificate -----\r\n`, certificate, '\r\n----- issuer -----\r\n', issuer, '\r\n----- callback -----\r\n', callback)
// })
// fakeServer.on('resumeSession', (sessionId, callback) => {
// log.debug(`【fakeServer resumeSession - ${hostname}:${port}】\r\n----- sessionId -----\r\n`, sessionId, '\r\n----- callback -----\r\n', callback)
// })
fakeServer.on('secureConnection', (tlsSocket) => {
log.debug(`【fakeServer secureConnection - ${hostname}:${port}】\r\n----- tlsSocket -----\r\n`, tlsSocket)
})
}
fakeServer.on('close', () => {
log.debug(`【fakeServer close - ${hostname}:${port}】no arguments...`)
})
fakeServer.on('connection', (socket) => {
log.debug(`【fakeServer connection - ${hostname}:${port}】\r\n----- socket -----\r\n`, socket)
})
fakeServer.on('checkContinue', (req, res) => {
log.debug(`【fakeServer checkContinue - ${hostname}:${port}】\r\n----- req -----\r\n`, req, '\r\n----- res -----\r\n', res)
})
fakeServer.on('checkExpectation', (req, res) => {
log.debug(`【fakeServer checkExpectation - ${hostname}:${port}】\r\n----- req -----\r\n`, req, '\r\n----- res -----\r\n', res)
})
fakeServer.on('connect', (req, socket, head) => {
log.debug(`【fakeServer resumeSession - ${hostname}:${port}】\r\n----- req -----\r\n`, req, '\r\n----- socket -----\r\n', socket, '\r\n----- head -----\r\n', head)
})
}
})()
})
serverPromiseObj.promise = promise
this.addServerPromise(serverPromiseObj)
return promise
}
reRankServer (index) {
// index ==> queue foot
this.queue.push((this.queue.splice(index, 1))[0])
}
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/tls/sniUtil.js
================================================
module.exports = function extractSNI (data) {
/*
From https://tools.ietf.org/html/rfc5246:
enum {
hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20)
(255)
} HandshakeType;
struct {
HandshakeType msg_type;
uint24 length;
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;
struct {
uint8 major;
uint8 minor;
} ProtocolVersion;
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
opaque SessionID<0..32>;
uint8 CipherSuite[2];
enum { null(0), (255) } CompressionMethod;
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;
*/
let end = data.length
// skip the record header
let pos = 5
// skip HandshakeType (you should already have verified this)
pos += 1
// skip handshake length
pos += 3
// skip protocol version (you should already have verified this)
pos += 2
// skip Random
pos += 32
// skip SessionID
if (pos > end - 1) {
return null
}
const sessionIdLength = data[pos]
pos += 1 + sessionIdLength
// skip CipherSuite
if (pos > end - 2) {
return null
}
const cipherSuiteLength = data[pos] << 8 | data[pos + 1]
pos += 2 + cipherSuiteLength
// skip CompressionMethod
if (pos > end - 1) {
return null
}
const compressionMethodLength = data[pos]
pos += 1 + compressionMethodLength
// verify extensions exist
if (pos > end - 2) {
return null
}
const extensionsLength = data[pos] << 8 | data[pos + 1]
pos += 2
// verify the extensions fit
const extensionsEnd = pos + extensionsLength
if (extensionsEnd > end) {
return null
}
end = extensionsEnd
/*
From https://tools.ietf.org/html/rfc5246
and http://tools.ietf.org/html/rfc6066:
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
signature_algorithms(13), (65535)
} ExtensionType;
enum {
server_name(0), max_fragment_length(1),
client_certificate_url(2), trusted_ca_keys(3),
truncated_hmac(4), status_request(5), (65535)
} ExtensionType;
struct {
NameType name_type;
select (name_type) {
case host_name: HostName;
} name;
} ServerName;
enum {
host_name(0), (255)
} NameType;
opaque HostName<1..2^16-1>;
struct {
ServerName server_name_list<1..2^16-1>
} ServerNameList;
*/
while (pos <= end - 4) {
const extensionType = data[pos] << 8 | data[pos + 1]
const extensionSize = data[pos + 2] << 8 | data[pos + 3]
pos += 4
if (extensionType === 0) { // ExtensionType was server_name(0)
// read ServerNameList length
if (pos > end - 2) {
return null
}
const nameListLength = data[pos] << 8 | data[pos + 1]
pos += 2
// verify we have enough bytes and loop over SeverNameList
let n = pos
pos += nameListLength
if (pos > end) {
return null
}
while (n < pos - 3) {
const nameType = data[n]
const nameLength = data[n + 1] << 8 | data[n + 2]
n += 3
// check if NameType is host_name(0)
if (nameType === 0) {
// verify we have enough bytes
if (n > end - nameLength) {
return null
}
// decode as ascii and return
const sniName = data.toString('ascii', n, n + nameLength)
return {
sniName,
start: n,
end: n + nameLength,
length: nameLength,
}
} else {
n += nameLength
}
}
} else { // ExtensionType was something we are not interested in
pos += extensionSize
}
}
return null
}
================================================
FILE: packages/mitmproxy/src/lib/proxy/tls/tlsUtils.js
================================================
const fs = require('node:fs')
const path = require('node:path')
const _ = require('lodash')
const forge = require('node-forge')
const log = require('../../../utils/util.log.server')
const config = require('../common/config')
// const colors = require('colors')
const utils = exports
const pki = forge.pki
// const os = require('os')
// let username = 'dev-sidecar'
// try {
// const user = os.userInfo()
// username = user.username
// } catch (e) {
// log.info('get userinfo error', e)
// }
utils.createCA = function (CN) {
const keys = pki.rsa.generateKeyPair(2048)
const cert = pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = `${Date.now()}`
cert.validity.notBefore = new Date(Date.now() - (60 * 60 * 1000))
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20)
const attrs = [{
name: 'commonName',
value: CN,
}, {
name: 'countryName',
value: 'CN',
}, {
shortName: 'ST',
value: 'GuangDong',
}, {
name: 'localityName',
value: 'ShenZhen',
}, {
name: 'organizationName',
value: 'dev-sidecar',
}, {
shortName: 'OU',
value: 'https://github.com/docmirror/dev-sidecar',
}]
cert.setSubject(attrs)
cert.setIssuer(attrs)
cert.setExtensions([{
name: 'basicConstraints',
critical: true,
cA: true,
}, {
name: 'keyUsage',
critical: true,
keyCertSign: true,
}, {
name: 'subjectKeyIdentifier',
}])
// self-sign certificate
cert.sign(keys.privateKey, forge.md.sha256.create())
return {
key: keys.privateKey,
cert,
}
}
utils.covertNodeCertToForgeCert = function (originCertificate) {
const obj = forge.asn1.fromDer(originCertificate.raw.toString('binary'))
return forge.pki.certificateFromAsn1(obj)
}
utils.createFakeCertificateByDomain = function (caKey, caCert, domain, mappingHostNames) {
// 作用域名
const altNames = []
mappingHostNames.forEach((mappingHostName) => {
altNames.push({
type: 2, // 1=电子邮箱、2=DNS名称
value: mappingHostName,
})
})
const keys = pki.rsa.generateKeyPair(2048)
const cert = pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = `${Date.now()}`
cert.validity.notBefore = new Date()
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1)
const attrs = [{
name: 'commonName',
value: domain,
}, {
name: 'countryName',
value: 'CN',
}, {
shortName: 'ST',
value: 'GuangDong',
}, {
name: 'localityName',
value: 'ShenZhen',
}, {
name: 'organizationName',
value: 'dev-sidecar',
}, {
shortName: 'OU',
value: 'https://github.com/docmirror/dev-sidecar',
}]
cert.setIssuer(caCert.subject.attributes)
cert.setSubject(attrs)
cert.setExtensions([{
name: 'basicConstraints',
critical: true,
cA: false,
},
// {
// name: 'keyUsage',
// critical: true,
// digitalSignature: true,
// contentCommitment: true,
// keyEncipherment: true,
// dataEncipherment: true,
// keyAgreement: true,
// keyCertSign: true,
// cRLSign: true,
// encipherOnly: true,
// decipherOnly: true
// },
{
name: 'subjectAltName',
altNames,
}, {
name: 'subjectKeyIdentifier',
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true,
}, {
name: 'authorityKeyIdentifier',
}])
cert.sign(caKey, forge.md.sha256.create())
return {
key: keys.privateKey,
cert,
}
}
utils.createFakeCertificateByCA = function (caKey, caCert, originCertificate) {
const certificate = utils.covertNodeCertToForgeCert(originCertificate)
const keys = pki.rsa.generateKeyPair(2048)
const cert = pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = certificate.serialNumber
cert.validity.notBefore = new Date()
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1)
cert.setSubject(certificate.subject.attributes)
cert.setIssuer(caCert.subject.attributes)
certificate.subjectaltname && (cert.subjectaltname = certificate.subjectaltname)
const subjectAltName = _.find(certificate.extensions, { name: 'subjectAltName' })
cert.setExtensions([{
name: 'basicConstraints',
critical: true,
cA: false,
}, {
name: 'keyUsage',
critical: true,
digitalSignature: true,
contentCommitment: true,
keyEncipherment: true,
dataEncipherment: true,
keyAgreement: true,
keyCertSign: true,
cRLSign: true,
encipherOnly: true,
decipherOnly: true,
}, {
name: 'subjectAltName',
altNames: subjectAltName.altNames,
}, {
name: 'subjectKeyIdentifier',
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true,
}, {
name: 'authorityKeyIdentifier',
}])
cert.sign(caKey, forge.md.sha256.create())
return {
key: keys.privateKey,
cert,
}
}
utils.isBrowserRequest = function (userAgent) {
return /Mozilla/i.test(userAgent)
}
//
// /^[^.]+\.a\.com$/.test('c.a.com')
//
utils.isMappingHostName = function (DNSName, hostname) {
if (DNSName === hostname) {
return true
}
let reg = DNSName.replace(/\./g, '\\.').replace(/\*/g, '[^.]+')
reg = `^${reg}$`
return (new RegExp(reg)).test(hostname)
}
utils.getMappingHostNamesFromCert = function (cert) {
let mappingHostNames = []
mappingHostNames.push(cert.subject.getField('CN') ? cert.subject.getField('CN').value : '')
const altNames = cert.getExtension('subjectAltName') ? cert.getExtension('subjectAltName').altNames : []
mappingHostNames = mappingHostNames.concat(_.map(altNames, 'value'))
return mappingHostNames
}
// sync
utils.initCA = function ({ caCertPath, caKeyPath }) {
try {
fs.accessSync(caCertPath, fs.F_OK)
fs.accessSync(caKeyPath, fs.F_OK)
// has exist
return {
caCertPath,
caKeyPath,
create: false,
}
} catch (e0) {
log.info('证书文件不存在,重新生成:', e0)
try {
const caObj = utils.createCA(config.caName)
const caCert = caObj.cert
const cakey = caObj.key
const certPem = pki.certificateToPem(caCert)
const keyPem = pki.privateKeyToPem(cakey)
fs.mkdirSync(path.dirname(caCertPath), { recursive: true })
fs.writeFileSync(caCertPath, certPem)
fs.writeFileSync(caKeyPath, keyPem)
log.info('生成证书文件成功,共2个文件:', caCertPath, caKeyPath)
} catch (e) {
log.error('生成证书文件失败:', caCertPath, caKeyPath, ', error:', e)
throw e
}
}
return {
caCertPath,
caKeyPath,
create: true,
}
}
================================================
FILE: packages/mitmproxy/src/lib/speed/SpeedTester.js
================================================
// const { exec } = require('node:child_process')
const net = require('node:net')
const _ = require('lodash')
const log = require('../../utils/util.log.server')
const config = require('./config.js')
// const isWindows = process.platform === 'win32'
const DISABLE_TIMEOUT = 60 * 60 * 1000
class SpeedTester {
constructor ({ hostname, port }) {
this.dnsMap = config.getConfig().dnsMap
this.hostname = hostname
this.port = port || 443
this.ready = false
this.alive = []
this.backupList = []
this.testCount = 0
this.lastReadTime = Date.now()
this.keepCheckIntervalId = false
this.tryTestCount = 0
this.test() // 异步:初始化完成后先测速一次
}
pickFastAliveIpObj () {
this.touch()
if (this.alive.length === 0) {
if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) {
this.testBackups() // 异步
} else if (this.tryTestCount % 10 === 0) {
this.test() // 异步
}
this.tryTestCount++
return null
}
return this.alive[0]
}
touch () {
this.lastReadTime = Date.now()
if (!this.keepCheckIntervalId) {
this.startChecker()
}
}
startChecker () {
if (this.keepCheckIntervalId) {
clearInterval(this.keepCheckIntervalId)
}
this.keepCheckIntervalId = setInterval(() => {
if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) {
// 超过很长时间没有访问,取消测试
clearInterval(this.keepCheckIntervalId)
this.keepCheckIntervalId = false
return
}
if (this.alive.length > 0) {
this.testBackups() // 异步
} else {
this.test() // 异步
}
}, config.getConfig().interval)
}
async getIpListFromDns (dnsMap) {
const ips = {}
const promiseList = []
for (const dnsKey in dnsMap) {
const dns = dnsMap[dnsKey]
const one = this.getFromOneDns(dns).then((ipList) => {
if (ipList && ipList.length > 0) {
for (const ip of ipList) {
ips[ip] = { dns: ipList.isPreSet === true ? '预设IP' : dnsKey }
}
}
})
promiseList.push(one)
}
await Promise.all(promiseList)
const items = []
for (const ip in ips) {
items.push({ host: ip, dns: ips[ip].dns })
}
return items
}
async getFromOneDns (dns) {
return await dns._lookupWithPreSetIpList(this.hostname)
}
async test () {
this.testCount++
log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`)
try {
const newList = await this.getIpListFromDns(this.dnsMap)
const newBackupList = [...newList, ...this.backupList]
this.backupList = _.unionBy(newBackupList, 'host')
await this.testBackups()
log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`)
if (config.notify) {
config.notify({ key: 'test' })
}
} catch (e) {
log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e)
}
}
async testBackups () {
if (this.backupList.length > 0) {
const aliveList = []
const testAll = []
for (const item of this.backupList) {
testAll.push(this.doTest(item, aliveList))
}
await Promise.all(testAll)
this.alive = aliveList
}
this.ready = true
}
async doTest (item, aliveList) {
try {
const ret = await this.testOne(item)
item.title = `${ret.by}测速成功:${ret.target}`
log.info(`[speed] test success: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}'`)
_.merge(item, ret)
aliveList.push({ ...ret, ...item })
aliveList.sort((a, b) => a.time - b.time)
this.backupList.sort((a, b) => {
if (a.time === b.time) {
return 0
}
if (a.time == null) {
return 1
}
if (b.time == null) {
return -1
}
return a.time - b.time
})
} catch (e) {
if (item.time == null) {
item.title = e.message
item.status = 'failed'
}
if (!e.message.includes('timeout')) {
log.warn(`[speed] test error: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}', errorMsg: ${e.message}`)
}
}
}
testByTCP (item) {
return new Promise((resolve, reject) => {
const { host, dns } = item
const startTime = Date.now()
let isOver = false
const timeout = 5000
let timeoutId = null
const client = net.createConnection({ host, port: this.port }, () => {
isOver = true
clearTimeout(timeoutId)
const connectionTime = Date.now()
resolve({ status: 'success', by: 'TCP', target: `${host}:${this.port}`, time: connectionTime - startTime })
client.end()
})
client.on('error', (e) => {
isOver = true
clearTimeout(timeoutId)
log.warn('[speed] test by TCP error: ', this.hostname, `➜ ${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
reject(e)
client.end()
})
timeoutId = setTimeout(() => {
if (isOver) {
return
}
log.warn('[speed] test by TCP timeout:', this.hostname, `➜ ${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
reject(new Error('timeout'))
client.end()
}, timeout)
})
}
// 暂不使用
// testByPing (item) {
// return new Promise((resolve, reject) => {
// const { host, dns } = item
// const startTime = Date.now()
//
// // 设置超时程序
// let isOver = false
// const timeout = 5000
// const timeoutId = setTimeout(() => {
// if (!isOver) {
// log.warn('[speed] test by PING timeout:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
// reject(new Error('timeout'))
// }
// }, timeout)
//
// // 协议选择(如强制ping6)
// const usePing6 = !isWindows && host.includes(':') // Windows无ping6命令
// const cmd = usePing6
// ? `ping6 -c 2 ${host}`
// : isWindows
// ? `ping -n 2 ${host}`
// : `ping -c 2 ${host}`
//
// log.debug('[speed] test by PING start:', this.hostname, `➜ ${host} from DNS '${dns}'`)
// exec(cmd, (error, stdout, _stderr) => {
// isOver = true
// clearTimeout(timeoutId)
//
// if (error) {
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 目标不可达或超时`)
// reject(new Error('目标不可达或超时'))
// return
// }
//
// // 提取延迟数据(正则匹配)
// const regex = /[=<](\d+(?:\.\d*)?)ms/gi // 适配Linux/Windows
// const times = []
// let match
// // eslint-disable-next-line no-cond-assign
// while ((match = regex.exec(stdout)) !== null) {
// times.push(Number.parseFloat(match[1]))
// }
//
// if (times.length === 0) {
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 无法解析延迟`)
// reject(new Error('无法解析延迟'))
// } else {
// // 计算平均延迟
// const avg = times.reduce((a, b) => a + b, 0) / times.length
// resolve({ status: 'success', by: 'PING', target: host, time: Math.round(avg) })
// }
// })
// })
// }
testOne (item) {
return new Promise((resolve, reject) => {
const thenFun = (ret) => {
resolve(ret)
}
// 先用TCP测速
this.testByTCP(item)
.then(thenFun)
.catch((e) => {
// // TCP测速失败,再用 PING 测速
// this.testByPing(item)
// .then(thenFun)
// .catch((e2) => {
// reject(new Error(`TCP测速失败:${e.message};PING测速失败:${e2.message};`))
// })
reject(new Error(`TCP测速失败:${item.host}:${this.port} ${e.message}`))
})
})
}
}
module.exports = SpeedTester
================================================
FILE: packages/mitmproxy/src/lib/speed/config.js
================================================
const config = {
dnsMap: {},
}
module.exports = {
getConfig () {
return config
},
notify: null,
}
================================================
FILE: packages/mitmproxy/src/lib/speed/index.js
================================================
const _ = require('lodash')
const log = require('../../utils/util.log.server')
const config = require('./config')
const SpeedTester = require('./SpeedTester.js')
const SpeedTestPool = {
}
function addSpeedTest (hostname, port) {
if (!port) {
const idx = hostname.indexOf(':')
if (idx > 0 && idx === hostname.lastIndexOf(':')) {
const arr = hostname.split(':')
hostname = arr[0]
port = Number.parseInt(arr[1]) || 443
} else {
port = 443
}
}
// 443端口不拼接在key上
const key = port === 443 ? hostname : `${hostname}:${port}`
if (SpeedTestPool[key] == null) {
return SpeedTestPool[key] = new SpeedTester({ hostname, port })
}
return SpeedTestPool[key]
}
function initSpeedTest (runtimeConfig) {
const { enabled, hostnameList } = runtimeConfig
const conf = config.getConfig()
_.merge(conf, runtimeConfig)
if (!enabled) {
return
}
_.forEach(hostnameList, (hostname) => {
addSpeedTest(hostname)
})
log.info('[speed] enabled,SpeedTestPool:', SpeedTestPool)
}
function getAllSpeedTester () {
const allSpeed = {}
if (config.getConfig().enabled) {
_.forEach(SpeedTestPool, (item, key) => {
allSpeed[key] = {
hostname: item.hostname,
port: item.port,
alive: item.alive,
backupList: item.backupList,
}
})
}
return allSpeed
}
function getSpeedTester (hostname, port) {
if (!config.getConfig().enabled) {
return null
}
return addSpeedTest(hostname, port)
}
// function registerNotify (notify) {
// config.notify = notify
// }
function reSpeedTest () {
_.forEach(SpeedTestPool, (item, _key) => {
item.test() // 异步
})
}
// action调用
function action (event) {
if (event.key === 'reTest') {
reSpeedTest()
} else if (event.key === 'getList') {
process.send({ type: 'speed', event: { key: 'getList', value: getAllSpeedTester() } })
}
}
module.exports = {
SpeedTester,
initSpeedTest,
getSpeedTester,
// getAllSpeedTester,
// registerNotify,
reSpeedTest,
action,
}
================================================
FILE: packages/mitmproxy/src/options.js
================================================
const fs = require('node:fs')
const path = require('node:path')
const lodash = require('lodash')
const dnsUtil = require('./lib/dns')
const interceptorImpls = require('./lib/interceptor')
const scriptInterceptor = require('./lib/interceptor/impl/res/script')
const { getTmpPacFilePath, downloadPacAsync, createOverwallMiddleware } = require('./lib/proxy/middleware/overwall')
const log = require('./utils/util.log.server')
const matchUtil = require('./utils/util.match')
// 处理拦截配置
function buildIntercepts (intercepts) {
// 自动生成script拦截器所需的辅助配置,降低使用`script拦截器`配置绝对地址和相对地址时的门槛
scriptInterceptor.handleScriptInterceptConfig(intercepts)
return intercepts
}
// 从拦截器配置中,获取exclusions字段,返回数组类型
function getExclusionArray (exclusions) {
let ret = null
if (Array.isArray(exclusions)) {
if (exclusions.length > 0) {
ret = exclusions
}
} else if (lodash.isObject(exclusions)) {
ret = []
for (const exclusion in exclusions) {
ret.push(exclusion)
}
if (ret.length === 0) {
return null
}
}
return ret
}
module.exports = (serverConfig) => {
const intercepts = matchUtil.domainMapRegexply(buildIntercepts(serverConfig.intercepts))
const whiteList = matchUtil.domainMapRegexply(serverConfig.whiteList)
const timeoutMapping = matchUtil.domainMapRegexply(serverConfig.setting.timeoutMapping)
const dnsMapping = serverConfig.dns.mapping
const setting = serverConfig.setting
if (!setting.script.dirAbsolutePath) {
setting.script.dirAbsolutePath = path.join(setting.rootDir, setting.script.defaultDir)
}
if (setting.verifySsl !== false) {
setting.verifySsl = true
}
setting.timeoutMapping = timeoutMapping
const overWallConfig = serverConfig.plugin.overwall
if (overWallConfig.pac && overWallConfig.pac.enabled) {
const pacConfig = overWallConfig.pac
// 自动更新 pac.txt
if (!pacConfig.pacFileAbsolutePath && pacConfig.autoUpdate) {
// 异步下载远程 pac.txt 文件,并保存到本地;下载成功后,需要重启代理服务才会生效
downloadPacAsync(pacConfig)
}
// 优先使用本地已下载的 pac.txt 文件
if (!pacConfig.pacFileAbsolutePath && fs.existsSync(getTmpPacFilePath())) {
pacConfig.pacFileAbsolutePath = getTmpPacFilePath()
log.info('读取已下载的 pac.txt 文件:', pacConfig.pacFileAbsolutePath)
}
if (!pacConfig.pacFileAbsolutePath) {
log.info('setting.rootDir:', setting.rootDir)
pacConfig.pacFileAbsolutePath = path.join(setting.rootDir, pacConfig.pacFilePath)
log.info('读取内置的 pac.txt 文件:', pacConfig.pacFileAbsolutePath)
if (pacConfig.autoUpdate) {
log.warn('远程 pac.txt 文件下载失败或还在下载中,现使用内置 pac.txt 文件:', pacConfig.pacFileAbsolutePath)
}
}
}
// 插件列表
const middlewares = []
// 梯子插件:如果启用了,则添加到插件列表中
const overwallMiddleware = createOverwallMiddleware(overWallConfig)
if (overwallMiddleware) {
middlewares.push(overwallMiddleware)
}
const preSetIpList = matchUtil.domainMapRegexply(serverConfig.preSetIpList)
const options = {
host: serverConfig.host,
port: serverConfig.port,
dnsConfig: {
preSetIpList,
dnsMap: dnsUtil.initDNS(serverConfig.dns.providers, preSetIpList),
mapping: matchUtil.domainMapRegexply(dnsMapping),
speedTest: serverConfig.dns.speedTest,
},
setting,
compatibleConfig: {
connect: serverConfig.compatible ? matchUtil.domainMapRegexply(serverConfig.compatible.connect) : {},
request: serverConfig.compatible ? matchUtil.domainMapRegexply(serverConfig.compatible.request) : {},
},
middlewares,
sslConnectInterceptor: (req, cltSocket, head) => {
const hostname = req.url.split(':')[0]
// 配置了白名单的域名,将跳过代理
const inWhiteList = !!matchUtil.matchHostname(whiteList, hostname, 'in whiteList')
if (inWhiteList) {
log.info(`为白名单域名,不拦截: ${hostname}`)
return false // 不拦截
}
// 配置了拦截的域名,将会被代理
const matched = matchUtil.matchHostname(intercepts, hostname, 'matched intercepts')
if ((!!matched) === true) {
log.debug(`拦截器拦截:${req.url}, matched:`, matched)
return matched // 拦截
}
return null // 不在白名单中,也未配置在拦截功能中,跳过当前拦截器,由下一个拦截器判断
},
createIntercepts: (context) => {
const rOptions = context.rOptions
const interceptOpts = matchUtil.matchHostnameAll(intercepts, rOptions.hostname, 'get interceptOpts')
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
return
}
const matchIntercepts = []
const matchInterceptsOpts = {}
for (const regexp in interceptOpts) { // 遍历拦截配置
// 判断是否匹配拦截器
const matched = matchUtil.isMatched(rOptions.path, regexp)
if (matched == null) { // 拦截器匹配失败
continue
}
// 获取拦截器
const interceptOpt = interceptOpts[regexp]
// interceptOpt.key = regexp
// 添加exclusions字段,用于排除某些路径
// @since 1.8.5
if (interceptOpt.exclusions) {
let isExcluded = false
try {
const exclusions = getExclusionArray(interceptOpt.exclusions)
if (exclusions) {
for (const exclusion of exclusions) {
if (matchUtil.isMatched(rOptions.path, exclusion)) {
log.debug(`拦截器配置排除了path:${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}, exclusion: '${exclusion}', interceptOpt:`, interceptOpt)
isExcluded = true
}
}
}
} catch (e) {
log.error(`判断拦截器是否排除当前path时出现异常, path: ${rOptions.path}, interceptOpt:`, interceptOpt, ', error:', e)
}
if (isExcluded) {
continue
}
}
log.debug(`拦截器匹配path成功:${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}, regexp: ${regexp}, interceptOpt:`, interceptOpt)
// log.info(`interceptor matched, regexp: '${regexp}' =>`, JSON.stringify(interceptOpt), ', url:', url)
for (const impl of interceptorImpls) {
// 根据拦截配置挑选合适的拦截器来处理
if (impl.is && impl.is(interceptOpt)) {
let action = 'add'
// 如果存在同名拦截器,则order值越大,优先级越高
const matchedInterceptOpt = matchInterceptsOpts[impl.name]
if (matchedInterceptOpt) {
if (matchedInterceptOpt.order >= (interceptOpt.order || 0)) {
log.warn(`duplicate interceptor: ${impl.name}, hostname: ${rOptions.hostname}`)
continue
}
action = 'replace'
}
const interceptor = { name: impl.name, priority: impl.priority }
if (impl.requestIntercept) {
// req拦截器
interceptor.requestIntercept = (context, req, res, ssl, next) => {
return impl.requestIntercept(context, interceptOpt, req, res, ssl, next, matched, interceptOpts.matched)
}
} else if (impl.responseIntercept) {
// res拦截器
interceptor.responseIntercept = (context, req, res, proxyReq, proxyRes, ssl, next) => {
return impl.responseIntercept(context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next, matched, interceptOpts.matched)
}
}
// log.info(`${action} interceptor: ${impl.name}, hostname: ${rOptions.hostname}, regexp: ${regexp}`)
if (action === 'add') {
matchIntercepts.push(interceptor)
} else {
matchIntercepts[matchedInterceptOpt.index] = interceptor
}
matchInterceptsOpts[impl.name] = {
order: interceptOpt.order || 0,
index: matchIntercepts.length - 1,
}
}
}
}
matchIntercepts.sort((a, b) => {
return a.priority - b.priority
})
// for (const interceptor of matchIntercepts) {
// log.info('interceptor:', interceptor.name, 'priority:', interceptor.priority)
// }
return matchIntercepts
},
}
if (setting.rootCaFile) {
options.caCertPath = setting.rootCaFile.certPath
options.caKeyPath = setting.rootCaFile.keyPath
}
return options
}
================================================
FILE: packages/mitmproxy/src/utils/util.js
================================================
// const os = require('os')
const log = require('./util.log.server')
const util = {
getNodeVersion () {
const version = process.version
log.info(version)
},
}
util.getNodeVersion()
module.exports = util
================================================
FILE: packages/mitmproxy/src/utils/util.log.server.js
================================================
const loggerFactory = require('@docmirror/dev-sidecar/src/utils/util.logger')
const logger = loggerFactory.getLogger('server')
module.exports = logger
================================================
FILE: packages/mitmproxy/src/utils/util.match.js
================================================
const lodash = require('lodash')
const log = require('./util.log.server')
const mergeApi = require('@docmirror/dev-sidecar/src/merge')
function isMatched (url, regexp) {
if (regexp === '.*' || regexp === '*' || regexp === 'true' || regexp === true) {
return [url]
}
try {
let urlRegexp = regexp
if (regexp[0] === '*' || regexp[0] === '?' || regexp[0] === '+') {
urlRegexp = `.${regexp}`
}
return url.match(urlRegexp)
} catch {
log.error('匹配串有问题:', regexp)
return null
}
}
function domainRegexply (target) {
if (target === '.*' || target === '*' || target === 'true' || target === true) {
return '^.*$'
}
return `^${target.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`
}
function domainMapRegexply (hostMap) {
if (hostMap == null) {
return { origin: {} }
}
const regexpMap = {}
const origin = {} // 用于快速匹配,见matchHostname、matchHostnameAll方法
lodash.each(hostMap, (value, domain) => {
try {
// 将域名匹配串格式如 `.xxx.com` 转换为 `*.xxx.com`
if (domain[0] === '.') {
if (hostMap[`*${domain}`] != null) {
return // 如果已经有匹配串 `*.xxx.com`,则忽略 `.xxx.com`
}
domain = `*${domain}`
}
if (domain.includes('*') || domain[0] === '^') {
const regDomain = domain[0] !== '^' ? domainRegexply(domain) : domain
regexpMap[regDomain] = value
if (domain.indexOf('*') === 0 && domain.lastIndexOf('*') === 0) {
origin[domain] = value
}
} else {
origin[domain] = value
}
} catch (e) {
log.error('匹配串有问题:', domain, e)
}
})
regexpMap.origin = origin
return regexpMap
}
function matchHostname (hostMap, hostname, action) {
// log.error('matchHostname:', action, hostMap)
if (hostMap == null) {
log.warn(`matchHostname: ${action}: '${hostname}' Not-Matched, hostMap is null`)
return null
}
if (hostMap.origin == null) {
log.warn(`matchHostname: ${action}: '${hostname}' Not-Matched, hostMap.origin is null`)
return null
}
// 域名快速匹配:直接匹配(优先级最高)
let value = hostMap.origin[hostname]
if (value != null) {
log.info(`matchHostname: ${action}: '${hostname}' -> { "${hostname}": ${JSON.stringify(value)} }`)
return value // 快速匹配成功
}
// 域名快速匹配:三种前缀通配符匹配
value = hostMap.origin[`*.${hostname}`]
if (value != null) {
log.info(`matchHostname: ${action}: '${hostname}' -> { "*.${hostname}": ${JSON.stringify(value)} }`)
return value // 快速匹配成功
}
value = hostMap.origin[`*${hostname}`]
if (value != null) {
log.info(`matchHostname: ${action}: '${hostname}' -> { "*${hostname}": ${JSON.stringify(value)} }`)
return value // 快速匹配成功
}
// 通配符匹配 或 正则表达式匹配
for (const regexp in hostMap) {
if (regexp === 'origin') {
continue
}
// 正则表达式匹配
if (hostname.match(regexp)) {
value = hostMap[regexp]
log.info(`matchHostname: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`)
return value
}
}
log.debug(`matchHostname: ${action}: '${hostname}' Not-Matched`)
}
function merge (oldObj, newObj) {
return lodash.mergeWith(oldObj, newObj, (objValue, srcValue) => {
if (lodash.isArray(objValue)) {
return srcValue
}
})
}
function matchHostnameAll (hostMap, hostname, action) {
// log.debug('matchHostname-all:', action, hostMap)
if (hostMap == null) {
log.warn(`matchHostname-all: ${action}: '${hostname}', hostMap is null`)
return null
}
if (hostMap.origin == null) {
log.warn(`matchHostname-all: ${action}: '${hostname}', hostMap.origin is null`)
return null
}
let values = {}
let value
// 通配符匹配 或 正则表达式匹配(优先级:1,最低)
for (const regexp in hostMap) {
if (regexp === 'origin') {
continue
}
// if (target.indexOf('*') < 0 && target[0] !== '^') {
// continue // 不是通配符匹配串,也不是正则表达式,跳过
// }
// 正则表达式匹配
const matched = hostname.match(regexp)
if (matched) {
value = hostMap[regexp]
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`)
values = merge(values, value)
// 设置matched
if (matched.length > 1) {
if (values.matched) {
// 合并array
matched.shift()
values.matched = [...values.matched, ...matched] // 拼接上多个matched
// 合并groups
if (matched.groups) {
values.matched.groups = merge(values.matched.groups, matched.groups)
} else {
values.matched.groups = matched.groups
}
} else {
values.matched = matched
}
}
}
}
// 域名快速匹配:直接匹配 或者 两种前缀通配符匹配
// 优先级:2
value = hostMap.origin[`*${hostname}`]
if (value) {
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "*${hostname}": ${JSON.stringify(value)} }`)
values = merge(values, value)
}
// 优先级:3
value = hostMap.origin[`*.${hostname}`]
if (value) {
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "*.${hostname}": ${JSON.stringify(value)} }`)
values = merge(values, value)
}
// 优先级:4,最高(注:优先级高的配置,可以覆盖优先级低的配置,甚至有空配置时,可以移除已有配置)
value = hostMap.origin[hostname]
if (value) {
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${hostname}": ${JSON.stringify(value)} }`)
values = merge(values, value)
}
if (!lodash.isEmpty(values)) {
mergeApi.deleteNullItems(values)
log.info(`matchHostname-all: ${action}: '${hostname}':`, JSON.stringify(values))
return values
} else {
log.debug(`matchHostname-all: ${action}: '${hostname}' Not-Matched`)
}
}
module.exports = {
isMatched,
domainRegexply,
domainMapRegexply,
matchHostname,
matchHostnameAll,
}
================================================
FILE: packages/mitmproxy/src/utils/util.process.js
================================================
module.exports = {
fireError (e) {
if (process.send) {
process.send({ type: 'error', event: e, message: e.message })
}
},
fireStatus (status) {
if (process.send) {
process.send({ type: 'status', event: status })
}
},
}
================================================
FILE: packages/mitmproxy/test/baiduOcrTest.js
================================================
const AipOcrClient = require('baidu-aip-sdk').ocr
// 设置APPID/AK/SK
const APP_ID = '101474620'
const API_KEY = 'fqCvIHGisGwpsglzV2wdxZJ5'
const SECRET_KEY = 'RhTOXUA4V6CrGuCTJJvUQ7z6Nl4m0Lij'
// 新建一个对象,建议只保存一个对象调用服务接口
const client = new AipOcrClient(APP_ID, API_KEY, SECRET_KEY)
// 图片:6525
const imageBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAeCAYAAAC7Q5mxAAAac0lEQVRoQ0VaCXScV3m9/+yrZtOMdsmbZMm2LNnybqdpnMQJjpsQMEsbEgItnFNoaFLasJ7Tc6A0kHIgoQRogabQkp4QSGJiCHZCbMdObMeOFVu7ZFm7RhpJs+9r7/eUgIPQNvr//33vfvfe777R+iZ7K+A/fUV9grb6id+X1WebzYZ4PA6TyQBNr0MymYRm0MNmt6NQzMFqMaJULiOdKgA6EzSdGZUSYDLoUM7lYDPqkYiHEaj2IplJIZHJwOasQrpYgdVqQyGZhs/jxfLysrqfwWiGw+HA/Pw86urqkMryHlYrorGE+r3FbkM6nYbT6US2kIdmN2JhYQEN7mrkeS0vr+mssmNobBTeRj9i2TRMbhvKugpOnTqF1EoYsaUVtARqUCzlkdHlMbO8gCp/ADAb4fLXYNu2bXDqbSilsrAVjfDbq2AqVBBeWYHbXYU4rxnPJOAN+KH1T6wWUIc/FZD3gobVAubzeeh0OhikIHxNiR9miwUVFjwWj6DEh5Ai6wxW6DQT/5BFrPD1OqCUzUIr5qHXlZHLZaDTa3BXV6NQriC4EuXfllHj9iIWiaKqqgp6vR6pTJrXMeD48eOqqJrBqO5//8cfgNPhQiafUxvq8XgwNTuDgr6Mnu4eBKemkI/FYTNbeP8SCwYYHWYspxOoWPWIJKJ468IFOPl7s6ZHldGEuYU5lAwVVMw6ZDVgmX9PpCCd5CYbzPjAnXej1uEFUjlYuCa+BI98/mE898Kv8PXHH8MDDz4IbfDGagEFeoI+KZ769t0C5ogi2e1iuYB0LguT2ayKKLseDM4B/HlDQwP8NY2qgKWiFFuKyEXwbysssMNmQrlc5FUryBOtOX5YbdxVoi20SPTU1mFpaUkV8dlnn0U4HMFnH3pIbR5fiiw34u8+9xC+850nFBoFgfX19bj/Ew/i0JHDOHToEPKJBOo81Sjm8rxPGQZ2zEoygunlRbgCHlx4+yIs3Pjp6+P46/s/LshQG9M7cBWTwVlk+XwFTYORCLZZ7UhFEigmsqir8mLHli50rFmPDO8bJWiKrICn1o9ILAZtaPxPBfxj8aSI2ioCBWnSrul8GvlCASarBaHlJfQPDGB5aRHsZrS1taGjfSvKJR3YVUQB4U/k8f9gY4snk1HVViV+n2QL601mXotoLpbRUF/Hdp3F8WPHia4M7jlyD+qbGtUGeYhO+VfgfY1mE1HM+xMh8i+VysBC5MvC06QVN7+udnkwPXFDtX6ukEOOC40XM8iUc7g6eA2TNyawcf16dLZvgtdRpfrJyU2b5zri3KQx/m3f0BDMZivcdheSyxHUuqpx867d0PFZvT43SqyHxWHB5atXsGVrF7SR9wrIy73Hf6sQXC1gmX+gcWeypZwqnvDfMPllYGgQBSJSENi+cSO2b9sNgg3ZTAlV5IwcecLM1xoNGubmp9DS0oRkOsmFp+En4tKZgkLEk09+lxRgwYc++GFUB3zIkHeEa6PRqFqINE6pVFKvFVqWjhAUrqxEEKitIWIs/HoFFp0BZGlEwyvwer2IJmOwCl/lkhgYG8Kbl85j7dq1uPvwXbwmEJycRnMjnymZgpW8usL2Les0IteMocERTBCpdpMF29q3sNhObN3UgemZSZI029/nwvMvvYi9+/ZDGxvrrVTY3Nq7HLjawETeuwXUGwyqdct6kj5vFE0mcK2/D5Mz0zBSIIpEzZrmFuzbeYC11NhCFTisTqIiDrvdingigppaH8KxMEKhRWzlrk3w4Z9++n8UNXzuc5/BSnSFaOX/WCjhN/loamoi8krkdROeeOIJPPLIIzh27CUcPXoUkUgEa1rWYm5ujm0URWvrRmTTGRTIn9VeHxJs5xx3U2c1Y3B8GIOjI8gUMujq6sTj3/oWfvrjn8DJTfjG1/4Ff//Qw3C7KHD8+yzbWmqRIw2UifY4uXlNXQOqybczMzNs+TJOnT2D7bt7sH5jK7uTdRtnAQVr74mIQh0vIgWUTjawdTIsoMFqVGgcnRhXD5RIJZWwlKjEtdV+3LT7ZiKA7VXQsYAOomKJRO/iBSgYi7Ooa6yj6hXwn//xYyLTjAce+KRC2XIkhN6rb+PBB0jIRPoAqcFMnrVzgYl4Ci6XC6+++iru/9jHEJxfhNNmV6KVzeZVa9uIDilkE3lYxEiQmqfQmB02JLMZnCfyliJhfp1EoKaGPM0W5GvkOk0NzWx7H4x60gOvVSbhCmX5fQEEZ+fw5HefwFe/+AUUyUuBQIAFTpFLL2FDeytcbOfZ2dnVAr6LuffApwooRCyf8+QtXhp2J+1MOoUrfVcxM0/xYJvJa1hCuGlLbt57C2yUfl3JoDhwcTEIT7WHViNN22LBUz/8PlwsaFdnF6YmpmHWWTA2fh3eGhf0RLIIw6b2DvT07EBoYRG1tbWYnZ6mPcqqIgqBi1iVcqU/OgOxO1LIR//xUTz22GO0GG4sceOqqfSpfBaDw8PoH+zj5pu5eLoAioYIk9PtVGgX1C8HV3DvPffSZtWQtSooF0vIEY3uKo+iMaG1KG2YFL1s4O9YDyvXk2AtRNy08ZErqzaGaJIbyC4UyiVaCo1kT49HHIYTMRaBPBGP4vjvX1aEvnv/Plx++y0UicBCJov7PnAf9EU9vDYvC7Ck/FKabfOTn7FdPHYcvP0glrm4wb4BWI1U0hjFxGigjWCrpGLc4VqloGJP9u/dxwe2IpVIQk9UhsNhlAplIteoWlXQIKrd1NTCDqiQSozsBoPqFKPFrLpDfOup189wIxdRz8IvrSxTXIhA3lPadHpuls9dIAD0bFEf7rjtDuRFmHgPp92B8FJYcbN4TZeHlERRKoAtTp5cXFnAjYkJTE1OQpu+0VchrpVCyu4IhFdvUmbLcYuItCK3wev34sRrryJEktbRcLZRON68+CaMJv6eZvfu2/4CVljUf8dfPI4wec3msuPofR+CwW7AufPnMDXFG3LBJp0RtZ5axV96m8YHK1B88khRTX0+H9aSU+tr68ltrQjOzCp7U8oWVSENOj2ef/55ZV0splVLY6eiSvEaW5oRJ0db6RqSqTiGqKjyN4loDO87cpe6TrFC1eZrZ1jAkbFxxbNZCtvGtRuwecNGWPVGRUEVAkn4X5xBibVIk34q+pJS7N5rvUoQa7zV0K6PXqno+MIyfZtwiphZgWuJNxIOKZEhzRSPqflpnDr3upL9fTffhByLffbCOU4OGpLxBO6+9QheOXYC+gyRSyP8wIP3K7dudlvx+9MnEFoJqXs0VNdix9btcOhsih7+cOE1blBZtYMgsMgWqqdKd27eTAtkhcNsR4bWx04DLC1b5gbkiHj5eoIo8Pv9ynzPBOfh81crkXN7PcruFPMF5Ficjo3tmKKF8XFyiHHhbp+odIJrmsWlty/znvSodA9Hj9yNx7/2DfzVhz6K3Tt20lhHYKtyUs05GS2H8PbAFYQpYPThtGG0SZkcfeDQpYrYBgM/dCyeXtqYKBTjLAs0sSVKhPzw+AhOv3EW7R0daKeki3144fiLSGQT8JADzQUjPnLkKPJhLs5ix8DwANq2tGMlG8OFqxeRzKXIK1U49Oe3QsuWcPbEGez/s5tg8dtw/vJFXLlyBR6XWxVQOGjvzl1Y27JGbUYjCzpPJCZiSaX4otLyfXd3N0UoTI6KwSDtViQALCbMkNyrqPCcYdR45+eoKJuRJG/VNjZgMbyMBX6YKDTHTv4WNTV+2PhqA2lif/cueGwu5S39dbWI8m+uz03hxuwkFih40sIFKnyaP/c63dAmpvorWaqWxqIJt1U4XqWJPBEI8X2ixTFyyjH6nrrmRphtVhy4+c84o2Zw7Le/gcFpQmg+iL2bd2HHxm6UE0UKr56vrcdUcAbDc+O4PHiF01Aah+96H9bXNyNG4vZZaEp57cGZUXR0bcbp06cxzJar9voVEtc2NmP3rl2IRzmK5Yt46cVjuP++B8g2RLiYLj6njh5TFpSjR3Ww9cOkhBQN/Pj4OKz0c34a62p60snxG/jKo1/GM888o4y3gwpqdTmJ/tfRNzXEwWARzaQULzf+g7ffRXiV8d//9TN88tOfwlvvvIOZpXlEOEjEORbK+vVGHR0CeZuTjzY1N1SRFily94SIRUhEoTQ+nJkFFLIO88XnyXdOrxtjN8ZRRVV0ENpRtmi6klXIqbNW49Deg6i2ehFfiXG3E7D5qnDq8jnEikkEObB7PG7csucAvGYnEgtR1UquFj/GZyYwOzWtFj43M0+P1wIrx8HfPP8Cfvb0z5GMxGjSS1gOLdG/2RTFBGidhMdE3Mx2M1ZSKc7XnJBGhhSP54i8TRva0NXWAZ/ThYGr/Uql5zg6JqnQgZZGzCdCGFgcJ2CoqMtRbG1pw4GuXfjmP38dn//8P3HNl3jdODKkGJ2dhp1i2rSmUQ0FIyMjWAkuro5yqmjcGeECeTjxYWlyxQrbQzjmmV8+q4ZkUeNHv/RFzAeD6neTRNj00iwVsoidrd2wlUzoad+GTDytKOAb3/kWdh26CZf6LqOlba2CfYCz5eGbb0clxiSFrn4hE4WvIUD3P4ix4RHOzU6M8rOOluJLj34BkaUIEvR3NSzYbQdvxaXzF1ULixrbnXbFrR4a9TjDiuFJetSRUQWE0PwC1tTU4+zJP+DJx7+NRDiubJDweo5cHGK4cPbaBcxXIrRrBbh0VlQbHdjT3g1dvoKrV95h1+RhclVhdHYKnsZaNG9Yh9aNG8jXabz+2ilUaLa1a4MXKk4iioMsYiRW4TwZlW5M3VC8lCAXGGhnLNyBPXv2qN2VWfPhf3gEB++8DQvJZYXYGtqXeygkWrqEhZkgkeGAnfJ/vv8KHzZMC7SsFlbF8WhNoBGd69qVmie0PEpGcjjJ/tyZc1haDKGluZnjoAON9U1oqm9QRlcmkiKFKxmJo4bRU3iZI5vPQ/QkeB29QtULv32J7cwggfcRpNZXB7Bn+w5GZjnUM74S42urcqkCRhJxnKCAJa15hht5GOgvO4nAXDiJBDvIzNEwSbVOUYicdCBdO3ugI78KijW6ktMsYJLjpDY92V9JUwElvRC78uRT30d1bYAcl6I9cKhxS2KljvY2bOMYJvLutjuVsX3z8gVMhRdUu1eZHejeuBlbWrdwkQlM0wSX6SULvObVob5VL0lDaybafWz5Fo5IJvJJkruf4oNGaTcmr08ofhPUyziW4cPnaTPEzmxgO5ro0eJ8aOG24PQcvvmvX6eY6fDUj76HqZk5ZcyTbGXZZBGfw4cP00um4KEpNrKoWc7RMnPlCZZJerirYwOIFiksTXWKx1vXrEMxnVdUkSfC5TkYcGLLli0KvcrSsUutVPiXf/c7hikrjLOuna+cfPUVzC7Mw+334b5PfFwtbHRiTLWpoFB29CAVU3hH44JijJscVLWxyeu4NNqvXL5MCBtobLdt6qZRJlqoaMJxDF0wfH0U1yeuq5vX0UqsX9MCFzcmIV5qfQue/80xZGiaZWNiHO8kMDBSBBxuD1GQYVFocC02tDQ1I8CEpopxk4mFiJDzKnox3y61yVIUNXaSP2XBDgqIjSCQf1YKhFifHLlekDg1NYOl6DKq6wMYGB1C56bN2Lt7DybIw6Ojo6QSO+Y5zvV0b0OtjIBch9TBqCe6+ey9rEtoiXnlzNRAZSEUQvO6FoUSGdc0ttZj3/4mdu7epcy1GNAD+/aqRUo6Mjo4BF+VG8tszUVmbpL2Zsl7oFpaOaJVyIk+qmlnVzfs5BDxZdlCVplaSWi8/JlwnPDRqbfeJI8tK+WVSUNQJhQSpvkVq8QnVoY+R3sjo9Z6bpJ8SChqYUs5adaHRgbR2NiIa9cYWbGIDhprcQox5nUydojZlulDJpaUiA05PE7KEKOcoEhIYdqI8q6uLnz1y19Be3s77ASIWJ+enh612RJgWOlF5fnEY84wwB0YGmELTw9WxIjKPFrhOGcmMa/QDswszKrUJU4OFFGpZ1vLsO4lh8jOyA3ylQLCjIscLgfHOKKdSqmXTDCdJfzrVDT+8isnsefAfpWu5IhUA1diYGsb2Ro2hxWLJPOf/98v0BCox+6dO1ChURbDKwb+yrWr9J9jsLCgYqmijOPrfDXYt2snXBahEY565D9blQN5orZAhJ88cUIVPBhcwO133oHuzq1qNBXhmeUML6mKbKSgXMZWCQ7mmSwdPnyEDmAamzd3Mrw4qZ4jwUjswL6bOB2xExhsSPokAa8cV8iMfubcWRZwdqgi8LfQiEpokKYndNJuvH7+LN39HEXFoqaTPJUnz5FNkpACUSmI0TjGUW85M9OIs3ANPE/ooDhIC1eYTMtYluV04yU1iNCkM0lYOCoViTzhGD19Z5Ajnwz+HRvaEeI00Uj/l2AnyBwus/fE3Ayu9fUp/rGTQ4UT2tetQ/emrQqpYp6/98On8NBn/lb5Shnbnv3Vc4zNupkCzSuHIDGcgECmp2Qq8ceJy8hnqfAs58D+/eRnBwPcKt5bEiAnfv3LX6tZ2OfzK6MtFFKh+KQJDjt/LvTw6plT0Hp7z1bWt27AT5/+KT78lx9VhJ7mgmR2Fc8kqiwtLNWXVEQcvvqeM6mV7fPciZfUaGMoalhb34guJtMSRKapfGKJNL5OBvIsi2YSdaS6ctJmIUpq5p4OBbFpayfGhoaVwn36bz7F5OYHyuK4qt0IRcM4c/Y0i7EIL38fXlyCn5nfnbfcqvioQoWmLCrREY6TzQ4tLeC1M6cVwqRwwtESlUnrZbk2aWMBQg3Rt30jU2WmzRqjpxgRJ/wpQ8Q8/ahEctVUcjMpbf36Vm4qz1boSeX3UtDXz52hkZ7oU0aaZyZKwQINdWrkEc7yMRaSqUQdJFmZC/J14hnlZE64IEvM/uKl51RcZCOtNzMA6GrbAicvztl79SyFtkJGQuEZdi5nU96LBZX0WNTQSLsjXGhmMaQgwjMyhwe5eTqbUan4cngJb/f28vf0q0R+DVFx5PY72FZJRKJJ1XZyBCCzPB8QfibbIhRyZjM5PaWeO8/CCYoNcqLI4kmi08AIq7VuHQauDbDVu7hO9icp5nv//iQT9h60tm3gwRkDZFJJWxuDBgbEUSJcwGDmcw6ODUObHOmtJMmBdZz7ZCbuZ1Qv8Y+Vvm9uIajicRmqvZxCRJXlxjJQS+YWSobx4qmXVdxfpv9bW9eIvT17UO1Y5Qx1MkAEqsMo7r5YFJlzq7gAKXKMXqxI+6SOJ8l5kr2JD5RQIMPRz+Ik93FeFa594YXnFYpE/RnL4Y5bD0Ijbeg0CVB54EWBC3ANwm+yQLEgMfKrIFO4Voq7wo0QhNbUBJiW81iWCZClZMY6jpciLEU+gwiNxGzSPRL4Cqp/8KMfocrtUmtQkRlfI4daeW60dr3vrYogJUa+EdGQAyLZRSF8I/9AbpQlnMQ8SjtLa8nhknBa0azhdzSjy4zti8kCmgN1TFp6UO+tUamJHBToyDOStKjpgEGpxFM7t3UrJGqkhQLnyitX3+EM7FFhZjt3eoKxl9FsYKpsQYScZeSp3vnzb6rfC3LFwuzZ0YNUNEXfxkMs8pdNTuvoXYUXE9yYkydP4uhHjqoi+BjsStvKeCl5o/wTUbEaLNDluCkMgGVzZGCQ14lgCX/3DfQrVd9EH1jPEOLll1/GR0hzIboWoZsgZ2htYWqoImORlcQoyiSLlN1yyM6ToGXxKpHhf8KPchMXRUZ2KKPjkH/+D0yCckw9+LqKgcO7i0LBZJAPV0XFrm9oQi1P3iTYlCQly0OcSIj+iwmJKOjbQ/3oY3KjsdXFe0nCIqpXkHySyKuwyJM8zBnkqJdhqCHtK35TpgxiBR5HgMKRUgGwWA05OZOWraet6X3nCtZRcESUBDmyJhFMdUhFr9tQ04DIwrLK/8bGxuCRmIsFl8N7qYFYH0GdXMvmsCt/2L19myq+FDzFMFkb7r9YEVg7eEHJz9489wZuu+Uggjx0lt2UF+oJ2bJWUhmatPEST8HC0QgtTAIJYw56cqDG+VFSDAuTGF2ZYsGZ1s9FdtBGCDH/7zO/wF3vO6wQJIZZWtnII89XLp5BhMeeEsrKOYWclG3fvp2zNLeMhzgSZLxBQRNkZFmYLa3tRN9OZPhOBY/DjXsPfwDPPvOsEgtZdB2RMsFzGwFDZ2enOoKVGVy4Ww65xHPKKCrTyuwURzvjKnAu8NBdqEQKJi0qVLWtZ7sqtpl1EDBJLSSxkp+l+CwG6oI2OHypIoLwMA+uv/34v6ngUqIiI3dU+EOKKzsqbeRgqy9QmXqvXsU8+bFk0SGukwvRXJZpgPm2Do+8DUIzq6hcDqj95EUd214QKNNBmqQsSW6YLt5sM+PiyDucNhI8BaMqkyZ8bh86OjYq4REEzPMkT45QpfUcbK3O1k3Y3NaKpdlFdXZrM9r4fOwAboCdgiSJTvPaNTwhzKvZXpRY0CZvH5HYTgonFLVIznvjjTeYapvVoZQ8o1DZvffeq0TSzq9lQ2RjZDNlipIESn4ndJSXEJYUpk0ujVUybKsshUSmC0lw7RzEey+9pY4IpVUFsuL1xB9KG4tCytzsDnhhcFMcyFc6+j5TmWemHJkMLKZE5TqxMOTCENumlamwCIS89SLC4vl5DrEYXcRSIcr3sQzwCJGxERfcXNek+EXyRmk7+SwBr4hCU109330Q4OGVHqU0D9tF2fl+HFmkCJSInpz9jvOAPJsmomm3VERHUXDx9G6ezy2cfpFrk/BCihkKLaCaPvX973+/AovM/2K2ZbKRAsokluNmqNM+bqqgT9As3RljnKed6T9TcTFRtss7DhgB+TgGFbibHu7mQ5/5rEKlnJkUGGHH+eYgMdoUP5Vey/tKNKumTuzkQEkGcbO0sKS7PPewso1NbEs55AlH46oIMo7JJgWIwmA4iLyjjP6xQSYgESxwoJcUpMzdpTVTeaQcEtVwFhWhqGMKY6ICp+jFPKQBOVVzEZk1bMnr168rtEmyJFxYU8PDe45wsgmyaEHOjfFJbCB6Rak93MBaTldWWqUIaUkKK60r+afQgAwKi2x/8ZxSOFFjNYpys9R9JGThad3/A6cC8EsBkFJyAAAAAElFTkSuQmCC'
const options = {}
options.detect_direction = 'false'
options.paragraph = 'false'
options.probability = 'false'
// 调用通用文字识别(高精度版)(异步)
client.accurateBasic(imageBase64, options).then((result) => {
console.log(JSON.stringify(result))
}).catch((err) => {
// 如果发生网络错误
console.log(err)
})
================================================
FILE: packages/mitmproxy/test/dnsSpeedTest.js
================================================
const dns = require('../src/lib/dns/index.js')
const SpeedTest = require('../src/lib/speed/index.js')
const SpeedTester = require('../src/lib/speed/SpeedTester.js')
const dnsMap = dns.initDNS({
cloudflare: {
type: 'https',
server: 'https://1.1.1.1/dns-query',
cacheSize: 1000,
},
// py233: { //污染
// type: 'https',
// server: ' https://i.233py.com/dns-query',
// cacheSize: 1000
// }
// google: { //不可用
// type: 'https',
// server: 'https://8.8.8.8/dns-query',
// cacheSize: 1000
// },
// dnsSB: { //不可用
// type: 'https',
// server: 'https://doh.dns.sb/dns-query',
// cacheSize: 1000
// }
})
SpeedTest.initSpeedTest({ hostnameList: {}, dnsMap })
const tester = new SpeedTester({ hostname: 'github.com' })
tester.test().then(() => {
console.log('github.com tester.alive = ', tester.alive)
})
================================================
FILE: packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs
================================================
import DNSOverHTTPS from "../src/lib/dns/https.js";
// 境外DNS的DoH配置sni测试
const servers = [
'https://dns.quad9.net/dns-query',
'https://max.rethinkdns.com/dns-query',
'https://sky.rethinkdns.com/dns-query',
'https://doh.opendns.com/dns-query',
'https://cloudflare-dns.com/dns-query',
'https://dns.google/dns-query',
'https://dns.bebasid.com/unfiltered',
'https://0ms.dev/dns-query',
'https://dns.decloudus.com/dns-query',
'https://wikimedia-dns.org/dns-query',
'https://doh.applied-privacy.net/query',
'https://private.canadianshield.cira.ca/dns-query',
// 'https://dns.controld.com/comss', // 可直连,无需SNI
'https://kaitain.restena.lu/dns-query',
'https://doh.libredns.gr/dns-query',
'https://doh.libredns.gr/ads',
'https://dns.switch.ch/dns-query',
'https://doh.nl.ahadns.net/dns-query',
'https://doh.la.ahadns.net/dns-query',
'https://dns.dnswarden.com/uncensored',
'https://doh.ffmuc.net/dns-query',
'https://dns.oszx.co/dns-query',
'https://doh.tiarap.org/dns-query',
'https://jp.tiarap.org/dns-query',
'https://dns.adguard.com/dns-query',
'https://rubyfish.cn/dns-query',
'https://i.233py.com/dns-query',
]
const hostnames = [
'github.com',
'mvnrepository.com',
]
const sni = 'baidu.com'
// const sni = ''
console.log(`\n--------------- 测试DoH的SNI功能:共 ${servers.length} 个服务,${hostnames.length} 个域名,SNI: ${sni || '无'} ---------------\n`)
let n = 0
let success = 0
let error = 0
const arr = []
function count (isSuccess, hostname, idx, dns, result, cost) {
if (isSuccess) {
success++
const ipList = []
for (const answer of result.answers) {
ipList[ipList.length] = answer.data;
}
arr[idx] = `${dns.dnsServer} : ${hostname} -> [ ${ipList.join(', ')} ] , cost: ${cost} ms`;
} else {
error++
}
n++
if (n === servers.length * hostnames.length) {
console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length * hostnames.length}, 成功:${success},失败:${error}`);
for (const item of arr) {
if (item) {
console.info(item);
}
}
console.info('=============================================================================\n\n')
}
}
let x = 0;
for (let i = 0; i < servers.length; i++) {
for (const hostname of hostnames) {
const dns = new DNSOverHTTPS(`dns-${i}-${hostname}`, null, null, servers[i], sni)
const start = Date.now()
const idx = x;
dns._doDnsQuery(hostname)
.then((result) => {
console.info(`===> ${dns.dnsServer}: ${hostname} ->`, result.answers, '\n\n')
count(true, hostname, idx, dns, result, Date.now() - start)
})
.catch((e) => {
console.error(`===> ${dns.dnsServer}: ${hostname} 失败:`, e, '\n\n')
count(false, hostname)
})
x++;
}
}
================================================
FILE: packages/mitmproxy/test/dnsTest-abroad-dot-sni.mjs
================================================
import DNSOverTLS from "../src/lib/dns/tls.js";
// 境外DNS的DoT配置sni测试
const servers = [
// 'dot.360.cn',
'1.1.1.1', // 可直连,无需SNI(有时候可以,有时候不行)
'one.one.one.one',
'cloudflare-dns.com',
'security.cloudflare-dns.com',
'family.cloudflare-dns.com',
'1dot1dot1dot1.cloudflare-dns.com',
'dot.sb',
'185.222.222.222',
'45.11.45.11',
'dns.adguard.com',
'dns.adguard-dns.com',
'dns-family.adguard.com',
'family.adguard-dns.com',
'dns-unfiltered.adguard.com',
'unfiltered.adguard-dns.com',
'dns.bebasid.com',
'unfiltered.dns.bebasid.com',
'antivirus.bebasid.com',
'internetsehat.bebasid.com',
'family-adblock.bebasid.com',
'oisd.dns.bebasid.com',
'hagezi.dns.bebasid.com',
'dns.cfiec.net',
'dns.opendns.com',
'familyshield.opendns.com',
'sandbox.opendns.com',
'family-filter-dns.cleanbrowsing.org',
'adult-filter-dns.cleanbrowsing.org',
'security-filter-dns.cleanbrowsing.org',
'p0.freedns.controld.com',
'p1.freedns.controld.com',
'p2.freedns.controld.com',
'p3.freedns.controld.com',
'dns.decloudus.com',
'getdnsapi.net',
'dnsovertls.sinodun.com',
'dnsovertls1.sinodun.com',
'dns.de.futuredns.eu.org',
'dns.us.futuredns.eu.org',
'unicast.censurfridns.dk',
]
const hostnames = [
'github.com',
'mvnrepository.com',
]
const sni = 'baidu.com'
// const sni = ''
console.log(`\n--------------- 测试DoT的SNI功能:共 ${servers.length} 个服务,${hostnames.length} 个域名,SNI: ${sni || '无'} ---------------\n`)
let n = 0
let success = 0
let error = 0
const arr = []
function count (isSuccess, hostname, idx, dns, result, cost) {
if (isSuccess) {
success++
const ipList = []
for (const answer of result.answers) {
ipList[ipList.length] = answer.data;
}
arr[idx] = `${dns.dnsServer} : ${hostname} -> [ ${ipList.join(', ')} ] , cost: ${cost} ms`;
} else {
error++
}
n++
if (n === servers.length * hostnames.length) {
console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length * hostnames.length}, 成功:${success},失败:${error}`);
for (const item of arr) {
if (item) {
console.info(item);
}
}
console.info('=============================================================================\n\n')
}
}
let x = 0;
for (let i = 0; i < servers.length; i++) {
for (const hostname of hostnames) {
const dns = new DNSOverTLS(`dns-${i}-${hostname}`, null, null, servers[i], null, sni)
const start = Date.now()
const idx = x;
dns._doDnsQuery(hostname)
.then((result) => {
console.info(`===> ${dns.dnsServer}: ${hostname} ->`, result.answers, '\n\n')
count(true, hostname, idx, dns, result, Date.now() - start)
})
.catch((e) => {
console.error(`===> ${dns.dnsServer}: ${hostname} 失败:`, e, '\n\n')
count(false, hostname)
})
x++;
}
}
================================================
FILE: packages/mitmproxy/test/dnsTest-abroad.mjs
================================================
import assert from 'node:assert'
import dns from '../src/lib/dns/index.js'
import matchUtil from '../src/utils/util.match.js'
const presetIp = '100.100.100.100'
const preSetIpList = matchUtil.domainMapRegexply({
'xxx.com': [
presetIp
]
})
// 境外DNS测试
const dnsProviders = dns.initDNS({
// udp
cloudflareUdp: {
server: 'udp://1.1.1.1',
},
quad9Udp: {
server: 'udp://9.9.9.9',
},
// tcp
cloudflareTcp: {
server: 'tcp://1.1.1.1',
},
quad9Tcp: {
server: 'tcp://9.9.9.9',
},
// https
cloudflare: {
server: 'https://1.1.1.1/dns-query',
},
quad9: {
server: 'https://9.9.9.9/dns-query',
forSNI: true,
},
rubyfish: {
server: 'https://rubyfish.cn/dns-query',
},
py233: {
server: ' https://i.233py.com/dns-query',
},
// tls
cloudflareTLS: {
type: 'tls',
server: '1.1.1.1',
servername: 'cloudflare-dns.com',
},
quad9TLS: {
server: 'tls://9.9.9.9',
servername: 'dns.quad9.net',
},
}, preSetIpList)
const hasPresetHostname = 'xxx.com'
const noPresetHostname = 'yyy.com'
const hostname1 = 'github.com'
const hostname2 = 'api.github.com'
const hostname3 = 'hk.docmirror.cn'
const hostname4 = 'github.docmirror.cn'
const hostname5 = 'gh.docmirror.top'
const hostname6 = 'gh2.docmirror.top'
let ip
console.log('\n--------------- test ForSNI ---------------\n')
console.log(`===> test ForSNI: ${dnsProviders.ForSNI.dnsName}`, '\n\n')
assert.strictEqual(dnsProviders.ForSNI, dnsProviders.quad9)
console.log('\n--------------- test PreSet ---------------\n')
ip = await dnsProviders.PreSet.lookup(hasPresetHostname)
console.log(`===> test PreSet: ${hasPresetHostname} ->`, ip, '\n\n')
console.log('\n\n')
assert.strictEqual(ip, presetIp) // 预设过IP,等于预设的IP
ip = await dnsProviders.PreSet.lookup(noPresetHostname)
console.log(`===> test PreSet: ${noPresetHostname} ->`, ip, '\n\n')
console.log('\n\n')
assert.strictEqual(ip, noPresetHostname) // 未预设IP,等于域名自己
console.log('\n--------------- test udp ---------------\n')
ip = await dnsProviders.cloudflareUdp.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.cloudflareUdp.dnsType, 'UDP')
ip = await dnsProviders.cloudflareUdp.lookup(hostname1)
console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.quad9Udp.dnsType, 'UDP')
ip = await dnsProviders.quad9Udp.lookup(hostname1)
console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test tcp ---------------\n')
ip = await dnsProviders.cloudflareTcp.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.cloudflareTcp.dnsType, 'TCP')
ip = await dnsProviders.cloudflareTcp.lookup(hostname1)
console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.quad9Tcp.dnsType, 'TCP')
ip = await dnsProviders.quad9Tcp.lookup(hostname1)
console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test https ---------------\n')
ip = await dnsProviders.cloudflare.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.cloudflare.dnsType, 'HTTPS')
ip = await dnsProviders.cloudflare.lookup(hostname1)
console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.quad9.dnsType, 'HTTPS')
ip = await dnsProviders.quad9.lookup(hostname1)
console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.rubyfish.dnsType, 'HTTPS')
ip = await dnsProviders.rubyfish.lookup(hostname1)
console.log(`===> test rubyfish: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.py233.dnsType, 'HTTPS')
ip = await dnsProviders.py233.lookup(hostname1)
console.log(`===> test py233: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test TLS ---------------\n')
ip = await dnsProviders.cloudflareTLS.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.cloudflareTLS.dnsType, 'TLS')
ip = await dnsProviders.cloudflareTLS.lookup(hostname1)
console.log(`===> test cloudflareTLS: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.quad9TLS.dnsType, 'TLS')
ip = await dnsProviders.quad9TLS.lookup(hostname1)
console.log(`===> test quad9TLS: ${hostname1} ->`, ip, '\n\n')
================================================
FILE: packages/mitmproxy/test/dnsTest.mjs
================================================
import assert from 'node:assert'
import dns from '../src/lib/dns/index.js'
import matchUtil from '../src/utils/util.match.js'
const presetIp = '100.100.100.100'
const preSetIpList = matchUtil.domainMapRegexply({
'xxx.com': [
presetIp
]
})
// 常用DNS测试
const dnsProviders = dns.initDNS({
// https
aliyun: {
type: 'https',
server: 'https://dns.alidns.com/dns-query',
cacheSize: 1000,
},
aliyun2: {
type: 'https',
server: 'dns.alidns.com', // 会自动补上 `https://` 和 `/dns-query`
cacheSize: 1000,
},
safe360: {
server: 'https://doh.360.cn/dns-query',
cacheSize: 1000,
forSNI: true,
},
// tls
aliyunTLS: {
server: 'tls://223.5.5.5:853',
cacheSize: 1000,
},
aliyunTLS2: {
server: 'tls://223.6.6.6',
cacheSize: 1000,
},
safe360TLS: {
server: 'tls://dot.360.cn',
cacheSize: 1000,
},
// tcp
googleTCP: {
type: 'tcp',
server: '8.8.8.8',
port: 53,
cacheSize: 1000,
},
aliyunTCP: {
server: 'tcp://223.5.5.5',
cacheSize: 1000,
},
// udp
googleUDP: {
// type: 'udp', // 默认是udp可以不用标
server: '8.8.8.8',
cacheSize: 1000,
},
aliyunUDP: {
server: 'udp://223.5.5.5',
cacheSize: 1000,
},
}, preSetIpList)
const hasPresetHostname = 'xxx.com'
const noPresetHostname = 'yyy.com'
const hostname1 = 'github.com'
const hostname2 = 'api.github.com'
const hostname3 = 'hk.docmirror.cn'
const hostname4 = 'github.docmirror.cn'
const hostname5 = 'gh.docmirror.top'
const hostname6 = 'gh2.docmirror.top'
let ip
console.log('\n--------------- test ForSNI ---------------\n')
console.log(`===> test ForSNI: ${dnsProviders.ForSNI.dnsName}`, '\n\n')
assert.strictEqual(dnsProviders.ForSNI, dnsProviders.safe360)
const dnsProviders2 = dns.initDNS({
aliyun: {
server: 'udp://223.5.5.5',
},
}, {})
console.log(`===> test ForSNI2: ${dnsProviders2.ForSNI.dnsName}`, '\n\n')
assert.strictEqual(dnsProviders2.ForSNI, dnsProviders2.PreSet) // 未配置forSNI的DNS时,默认使用PreSet作为ForSNI
console.log('\n--------------- test PreSet ---------------\n')
ip = await dnsProviders.PreSet.lookup(hasPresetHostname)
console.log(`===> test PreSet: ${hasPresetHostname} ->`, ip, '\n\n')
console.log('\n\n')
assert.strictEqual(ip, presetIp) // 预设过IP,等于预设的IP
ip = await dnsProviders.PreSet.lookup(noPresetHostname)
console.log(`===> test PreSet: ${noPresetHostname} ->`, ip, '\n\n')
console.log('\n\n')
assert.strictEqual(ip, noPresetHostname) // 未预设IP,等于域名自己
console.log('\n--------------- test https ---------------\n')
ip = await dnsProviders.aliyun.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.aliyun.dnsType, 'HTTPS')
ip = await dnsProviders.aliyun.lookup(hostname1)
console.log(`===> test aliyun: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.aliyun2.dnsType, 'HTTPS')
ip = await dnsProviders.aliyun2.lookup(hostname1)
console.log(`===> test aliyun2: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.safe360.dnsType, 'HTTPS')
ip = await dnsProviders.safe360.lookup(hostname1)
console.log(`===> test safe360: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test TLS ---------------\n')
ip = await dnsProviders.aliyunTLS.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.aliyunTLS.dnsType, 'TLS')
ip = await dnsProviders.aliyunTLS.lookup(hostname1)
console.log(`===> test aliyunTLS: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.aliyunTLS2.dnsType, 'TLS')
ip = await dnsProviders.aliyunTLS2.lookup(hostname1)
console.log(`===> test aliyunTLS2: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.safe360TLS.dnsType, 'TLS')
ip = await dnsProviders.safe360TLS.lookup(hostname1)
console.log(`===> test safe360TLS: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test TCP ---------------\n')
ip = await dnsProviders.googleTCP.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.googleTCP.dnsType, 'TCP')
ip = await dnsProviders.googleTCP.lookup(hostname1)
console.log(`===> test googleTCP: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.aliyunTCP.dnsType, 'TCP')
ip = await dnsProviders.aliyunTCP.lookup(hostname1)
console.log(`===> test aliyunTCP: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test UDP ---------------\n')
ip = await dnsProviders.googleUDP.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.googleUDP.dnsType, 'UDP')
ip = await dnsProviders.googleUDP.lookup(hostname1)
console.log(`===> test googleUDP: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.aliyunUDP.dnsType, 'UDP')
ip = await dnsProviders.aliyunUDP.lookup(hostname1)
console.log(`===> test aliyunUDP: ${hostname1} ->`, ip, '\n\n')
dnsProviders.aliyunUDP.lookup(hostname1).then(ip0 => {
console.log(`===> test aliyunUDP: ${hostname1} ->`, ip0, '\n\n')
})
dnsProviders.aliyunUDP.lookup(hostname2).then(ip0 => {
console.log(`===> test aliyunUDP: ${hostname2} ->`, ip0, '\n\n')
})
dnsProviders.aliyunUDP.lookup('baidu.com').then(ip0 => {
console.log('===> test aliyunUDP: baidu.com ->', ip0, '\n\n')
})
dnsProviders.aliyunUDP.lookup('gitee.com').then(ip0 => {
console.log('===> test aliyunUDP: gitee.com ->', ip0, '\n\n')
})
================================================
FILE: packages/mitmproxy/test/lodashTest.js
================================================
const assert = require('node:assert')
const lodash = require('lodash')
// test lodash.isEqual
const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3]
const arr3 = [3, 2, 1]
assert.strictEqual(lodash.isEqual(arr1, arr2), true)
assert.strictEqual(lodash.isEqual(arr1.sort(), arr3.sort()), true)
// test lodash.isEmpty
function isEmpty (obj) {
return obj == null || (lodash.isObject(obj) && lodash.isEmpty(obj))
}
// true
assert.strictEqual(isEmpty(null), true)
assert.strictEqual(isEmpty({}), true)
assert.strictEqual(isEmpty([]), true)
// false
assert.strictEqual(isEmpty(true), false)
assert.strictEqual(isEmpty(false), false)
assert.strictEqual(isEmpty(1), false)
assert.strictEqual(isEmpty(0), false)
assert.strictEqual(isEmpty(-1), false)
assert.strictEqual(isEmpty(''), false)
assert.strictEqual(isEmpty('1'), false)
// test lodash.unionBy
const list = [
{ host: 1, port: 1, dns: 2 },
{ host: 1, port: 1, dns: 3 },
{ host: 1, port: 2, dns: 3 },
{ host: 1, port: 2, dns: 3 },
]
console.info(lodash.unionBy(list, 'host', 'port'))
================================================
FILE: packages/mitmproxy/test/matchTest.js
================================================
const assert = require('node:assert')
const name = '/docmirror/dev-sidecar/raw/master/doc/index.png'
// https://raw.fastgit.org/docmirror/dev-sidecar/master/doc/index.png
const ret = name.replace(/^(.+)\/raw\/(.+)$/, 'raw.fastgit.org$1/$2')
console.log(ret)
assert.strictEqual(ret, 'raw.fastgit.org/docmirror/dev-sidecar/master/doc/index.png')
const reg = /^\/[^/]+\/[^/]+$/
console.log('/greper/d2-crud-plus/blob/master/.eslintignore'.match(reg))
assert.strictEqual('/greper/d2-crud-plus/blob/master/.eslintignore'.match(reg), null)
const chunk = Buffer.from('
')
const script = ''
const index = chunk.indexOf('')
const scriptBuf = Buffer.from(script)
const chunkNew = Buffer.alloc(chunk.length + scriptBuf.length)
chunk.copy(chunkNew, 0, 0, index)
scriptBuf.copy(chunkNew, index, 0)
chunk.copy(chunkNew, index + scriptBuf.length, index)
console.log(chunkNew.toString())
assert.strictEqual(chunkNew.toString(), '
')
const reg2 = /aaaa/i
console.log(reg2.test('aaaa')) // true
assert.strictEqual(reg2.test('aaaa'), true)
const reg3 = '/aaaa/i'
console.log(new RegExp(reg3).test('aaaa')) // false
assert.strictEqual(new RegExp(reg3).test('aaaa'), false)
================================================
FILE: packages/mitmproxy/test/matchUtilTest.js
================================================
const assert = require('node:assert')
const matchUtil = require('../src/utils/util.match')
const hostMap = matchUtil.domainMapRegexply({
'aaa.com': true,
'*bbb.com': true,
'*.ccc.com': true,
'^.{1,3}ddd.com$': true,
'*.cn': true,
'.github.com': true,
'*.eee.com': true,
'.eee.com': false, // 此配置将被忽略,因为有 '*.eee.com' 了,优先级更高
})
console.log(hostMap)
assert.strictEqual(hostMap['^.*bbb\\.com$'], true)
assert.strictEqual(hostMap['^.*\\.ccc\\.com$'], true)
assert.strictEqual(hostMap['^.{1,3}ddd.com$'], true)
assert.strictEqual(hostMap['^.*\\.cn$'], true)
assert.strictEqual(hostMap['^.*\\.github\\.com$'], true)
assert.strictEqual(hostMap['^.*\\.github\\.com$'], true)
assert.strictEqual(hostMap['^.*\\.eee\\.com$'], true)
const origin = hostMap.origin
assert.strictEqual(origin['aaa.com'], true)
assert.strictEqual(origin['*bbb.com'], true)
assert.strictEqual(origin['*.ccc.com'], true)
assert.strictEqual(origin['*.cn'], true)
assert.strictEqual(origin['*.github.com'], true)
assert.strictEqual(origin['.eee.com'], undefined)
const value11 = matchUtil.matchHostname(hostMap, 'aaa.com', 'test1.1')
const value12 = matchUtil.matchHostname(hostMap, 'aaaa.com', 'test1.2')
const value13 = matchUtil.matchHostname(hostMap, 'aaaa.comx', 'test1.3')
console.log('test1: aaa.com')
assert.strictEqual(value11, true)
assert.strictEqual(value12, undefined)
assert.strictEqual(value13, undefined)
const value21 = matchUtil.matchHostname(hostMap, 'bbb.com', 'test2.1')
const value22 = matchUtil.matchHostname(hostMap, 'xbbb.com', 'test2.2')
const value23 = matchUtil.matchHostname(hostMap, 'bbb.comx', 'test2.3')
const value24 = matchUtil.matchHostname(hostMap, 'x.bbb.com', 'test2.4')
console.log('test2: *bbb.com')
assert.strictEqual(value21, true)
assert.strictEqual(value22, true)
assert.strictEqual(value23, undefined)
assert.strictEqual(value24, true)
const value31 = matchUtil.matchHostname(hostMap, 'ccc.com', 'test3.1')
const value32 = matchUtil.matchHostname(hostMap, 'x.ccc.com', 'test3.2')
const value33 = matchUtil.matchHostname(hostMap, 'xccc.com', 'test3.3')
console.log('test3: *.ccc.com')
assert.strictEqual(value31, true)
assert.strictEqual(value32, true)
assert.strictEqual(value33, undefined)
const value41 = matchUtil.matchHostname(hostMap, 'ddd.com', 'test4.1')
const value42 = matchUtil.matchHostname(hostMap, 'x.ddd.com', 'test4.2')
const value43 = matchUtil.matchHostname(hostMap, 'xddd.com', 'test4.3')
console.log('test4: ^.{1,3}ddd.com$')
assert.strictEqual(value41, undefined)
assert.strictEqual(value42, true)
assert.strictEqual(value43, true)
const value51 = matchUtil.matchHostname(hostMap, 'zzz.cn', 'test5.1')
const value52 = matchUtil.matchHostname(hostMap, 'x.zzz.cn', 'test5.2')
const value53 = matchUtil.matchHostname(hostMap, 'zzz.cnet.com', 'test5.3')
console.log('test5: *.cn')
assert.strictEqual(value51, true)
assert.strictEqual(value52, true)
assert.strictEqual(value53, undefined)
const value61 = matchUtil.matchHostname(hostMap, 'github.com', 'test6.1')
const value62 = matchUtil.matchHostname(hostMap, 'api.github.com', 'test6.2')
const value63 = matchUtil.matchHostname(hostMap, 'aa.bb.github.com', 'test6.3')
const value64 = matchUtil.matchHostname(hostMap, 'aaagithub.com', 'test6.4')
console.log('test6: .github.com')
assert.strictEqual(value61, true)
assert.strictEqual(value62, true)
assert.strictEqual(value63, true)
assert.strictEqual(value64, undefined)
================================================
FILE: packages/mitmproxy/test/monkeyTest.js
================================================
const assert = require('node:assert')
const monkey = require('../src/lib/monkey')
let scripts
try {
scripts = monkey.load('../gui/extra/scripts/') // 相对于 mitmproxy 目录的相对路径,而不是当前 test 目录的。
} catch {
scripts = monkey.load('../../gui/extra/scripts/') // 相对于 当前 test 目录的相对路径
}
// console.log(scripts)
assert.strictEqual(scripts.github != null, true)
assert.strictEqual(scripts.google != null, true)
assert.strictEqual(scripts.tampermonkey != null, true)
================================================
FILE: packages/mitmproxy/test/pacTest.js
================================================
const assert = require('node:assert')
const pac = require('../src/lib/proxy/middleware/source/pac')
const pacClient = pac.createPacClient('../gui/extra/pac/pac.txt') // 相对于 mitmproxy 目录的相对路径,而不是当前 test 目录的。
const string = pacClient.FindProxyForURL('https://www.facebook.com', 'www.facebook.com')
console.log(`facebook: ${string}`)
assert.strictEqual(string, pacClient.proxyUrl)
const string2 = pacClient.FindProxyForURL('https://http2.golang.org', 'http2.golang.org')
console.log(`golang: ${string2}`)
assert.strictEqual(string2, 'DIRECT;')
================================================
FILE: packages/mitmproxy/test/proxyTest.js
================================================
// const http = require('node:http')
//
// const options = {
// headers: {
// 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
// },
// lookup (hostname, options, callback) {
// const ip = '106.52.191.148'
// console.log('lookup')
// callback(null, ip, 4)
// },
// }
//
// const request = http.get('http://test.target/', options, (response) => {
// response.on('data', (data) => {
// process.stdout.write(data)
// })
// })
//
// request.on('error', (error) => {
// console.log(error)
// })
================================================
FILE: packages/mitmproxy/test/responseReplaceTest.js
================================================
const assert = require('node:assert')
const responseReplace = require('../src/lib/interceptor/impl/res/responseReplace')
const headers = {}
const res = {
setHeader: (key, value) => {
headers[key] = value
},
}
const proxyRes = {
rawHeaders: [
'Content-Type', 'application/json; charset=utf-8',
'Content-Length', '2',
'ETag', 'W/"2"',
'Date', 'Thu, 01 Jan 1970 00:00:00 GMT',
'Connection', 'keep-alive',
],
}
const newHeaders = {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': '3',
'xxx': 1,
'Date': '[remove]',
'yyy': '[remove]',
}
const result = responseReplace.replaceResponseHeaders(newHeaders, res, proxyRes)
console.log(proxyRes.rawHeaders)
console.log(headers)
console.log(result)
assert.deepStrictEqual(proxyRes.rawHeaders, [
'Content-Type', 'application/json; charset=utf-8',
'Content-Length', '3',
'ETag', 'W/"2"',
'Date', '',
'Connection', 'keep-alive'
])
assert.deepStrictEqual(headers, {
xxx: 1,
})
assert.deepStrictEqual(result, {
'content-length': '2',
'date': 'Thu, 01 Jan 1970 00:00:00 GMT',
'xxx': null,
})
================================================
FILE: packages/mitmproxy/test/sha256Test.js
================================================
// 需要时,在 package.json 中添加以下依赖:
// "devDependencies": {
// "crypto-js": "^4.2.0"
// }
// const CryptoJs = require('crypto-js')
//
// const ret = CryptoJs.SHA256('111111111111')
// console.log(ret.toString(CryptoJs.enc.Base64))
// console.log(1 / 2)
================================================
FILE: packages/mitmproxy/test/utilTest.js
================================================
const assert = require('node:assert')
const util = require('../src/lib/proxy/common/util')
let arr
arr = util.parseHostnameAndPort('www.baidu.com')
console.log('arr1:', arr)
assert.strictEqual(arr.length === 1, true) // true
assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
arr = util.parseHostnameAndPort('www.baidu.com', 80)
console.log('arr2:', arr)
assert.strictEqual(arr.length === 2, true) // true
assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
assert.strictEqual(arr[1] === 80, true) // true
arr = util.parseHostnameAndPort('www.baidu.com:8080')
console.log('arr3:', arr)
assert.strictEqual(arr.length === 2, true) // true
assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
assert.strictEqual(arr[1] === 8080, true) // true
arr = util.parseHostnameAndPort('www.baidu.com:8080', 8080)
console.log('arr4:', arr)
assert.strictEqual(arr.length === 2, true) // true
assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
assert.strictEqual(arr[1] === 8080, true) // true
arr = util.parseHostnameAndPort('[2001:abcd::1]')
console.log('arr5:', arr)
assert.strictEqual(arr.length === 1, true) // true
assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // ture
arr = util.parseHostnameAndPort('[2001:abcd::1]', 80)
console.log('arr6:', arr)
assert.strictEqual(arr.length === 2, true) // true
assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // ture
assert.strictEqual(arr[1] === 80, true) // ture
arr = util.parseHostnameAndPort('[2001:abcd::1]:8080')
console.log('arr7:', arr)
assert.strictEqual(arr.length === 2, true) // true
assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // true
assert.strictEqual(arr[1] === 8080, true) // ture
arr = util.parseHostnameAndPort('[2001:abcd::1]:8080', 8080)
console.log('arr8:', arr)
assert.strictEqual(arr.length === 2, true) // true
assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // true
assert.strictEqual(arr[1] === 8080, true) // ture
================================================
FILE: pnpm-workspace.yaml
================================================
packages:
# all packages in subdirectories of packages/ and components/
- packages/*
# exclude packages that are inside test directories
- '!**/test/**'
================================================
FILE: test/test.js
================================================
// const cmd1 = require('node-cmd')
// cmd1.get('set',
// function (err, data, stderr) {
// console.log('cmd complete:', err, data, stderr)
// if (err) {
// console.error('cmd 命令执行错误:', err, stderr)
// } else {
// console.log('cmd 命令执行结果:', data)
// }
// }
// )
// const process = require('child_process')
//
// const cmd = 'set'
// process.exec(cmd, function (error, stdout, stderr) {
// console.log('error:' + error)
// console.log('stdout:' + stdout)
// console.log('stderr:' + stderr)
// })
// const fs = require('fs')
// const content = fs.readFileSync('C:\\Users\\Administrator\\.dev-sidecar\\dev-sidecar.ca.crt')
// console.log('content:',JSON.stringify(content.toString().replace(new RegExp('\r\n','g'),'\n')))
// function testCa () {
// const https = require('https')
// const fs = require('fs')
// process.env.NODE_EXTRA_CA_CERTS = 'C:\\Users\\Administrator\\.dev-sidecar\\dev-sidecar.ca.crt'
// process.env.GLOBAL_AGENT_HTTP_PROXY = "http://127.0.0.1:31181"
// process.env.GLOBAL_AGENT_HTTPS_PROXY = "http://127.0.0.1:31181"
// fs.readFileSync(process.env.NODE_EXTRA_CA_CERTS)
//
// const options = {
// agent : new https.Agent({
// proxy: "http://127.0.0.1:31181"
// })
// }
// console.log('options', options)
//
// https.get('https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js',options, (res) => {
// console.log('状态码:', res.statusCode)
// console.log('请求头:', res.headers)
//
// res.on('data', (d) => {
// process.stdout.write(d)
// })
// }).on('error', (e) => {
// console.error(e)
// })
// }
function testRequest () {
// process.env.NODE_EXTRA_CA_CERTS='C:\\Users\\Administrator\\.dev-sidecar\\dev-sidecar.ca.crt'
console.log(process.env.NODE_EXTRA_CA_CERTS)
const request = require('request').defaults({
proxy: 'http://127.0.0.1:31181',
})
request('https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js', (error, response, body) => {
if (error) {
console.error(error)
} else {
console.log(body)
}
})
}
// testCa()
testRequest()
================================================
FILE: test/testDns.js
================================================
console.log('www.baidu.com'.match('.*.baidu.com'))