├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── LICENSE
├── README.md
├── front
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── .prettierrc
├── .vscode
│ └── settings.json
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── pay
│ │ ├── alipay.png
│ │ └── weipay.png
│ └── team_header
│ │ ├── Black-Hole.png
│ │ ├── NISAL.png
│ │ └── monkeyWie.png
├── src
│ ├── App.vue
│ ├── common
│ │ ├── http.js
│ │ └── native.js
│ ├── components
│ │ ├── ExtensionSetting.vue
│ │ ├── FileChoose
│ │ │ └── index.vue
│ │ ├── Table
│ │ │ └── index.vue
│ │ └── Task
│ │ │ ├── Create.vue
│ │ │ └── Resolve.vue
│ ├── i18n
│ │ ├── en-US.js
│ │ ├── zh-CN.js
│ │ └── zh-TW.js
│ ├── main.js
│ ├── router.js
│ ├── store.js
│ └── views
│ │ ├── About.vue
│ │ ├── Extension.vue
│ │ ├── Setting.vue
│ │ ├── Support.vue
│ │ └── Tasks.vue
└── vue.config.js
├── main
├── .gitignore
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── org
│ │ └── pdown
│ │ └── gui
│ │ ├── DownApplication.java
│ │ ├── com
│ │ ├── Browser.java
│ │ ├── CheckboxMenuItemGroup.java
│ │ └── Components.java
│ │ ├── content
│ │ └── PDownConfigContent.java
│ │ ├── entity
│ │ └── PDownConfigInfo.java
│ │ ├── extension
│ │ ├── ContentScript.java
│ │ ├── ExtensionConfig.java
│ │ ├── ExtensionContent.java
│ │ ├── ExtensionInfo.java
│ │ ├── HookScript.java
│ │ ├── Meta.java
│ │ ├── Setting.java
│ │ ├── jsruntime
│ │ │ ├── JavascriptEngine.java
│ │ │ └── polyfill
│ │ │ │ ├── Window.java
│ │ │ │ └── property
│ │ │ │ ├── Console.java
│ │ │ │ ├── Document.java
│ │ │ │ └── XMLHttpRequest.java
│ │ ├── mitm
│ │ │ ├── intercept
│ │ │ │ ├── AjaxIntercept.java
│ │ │ │ ├── CookieIntercept.java
│ │ │ │ ├── ScriptIntercept.java
│ │ │ │ └── SniffIntercept.java
│ │ │ ├── server
│ │ │ │ └── PDownProxyServer.java
│ │ │ ├── ssl
│ │ │ │ └── PDownCACertFactory.java
│ │ │ └── util
│ │ │ │ ├── ExtensionCertUtil.java
│ │ │ │ ├── ExtensionProxyUtil.java
│ │ │ │ └── WinInet.java
│ │ └── util
│ │ │ └── ExtensionUtil.java
│ │ ├── http
│ │ ├── EmbedHttpServer.java
│ │ ├── controller
│ │ │ ├── ApiController.java
│ │ │ ├── DefaultController.java
│ │ │ ├── NativeController.java
│ │ │ └── PacController.java
│ │ └── util
│ │ │ └── HttpHandlerUtil.java
│ │ ├── rest
│ │ └── HttpDownAppCallback.java
│ │ ├── update
│ │ ├── CheckUpdate.java
│ │ └── VersionInfo.java
│ │ └── util
│ │ ├── AppUtil.java
│ │ ├── ConfigUtil.java
│ │ ├── ExecUtil.java
│ │ └── I18nUtil.java
│ └── resources
│ ├── application-dev.yml
│ ├── application-prd.yml
│ ├── application.yml
│ ├── banner.txt
│ ├── extension
│ └── runtime.js
│ ├── i18n
│ ├── messages_en-US.yml
│ ├── messages_zh-CN.yml
│ └── messages_zh-TW.yml
│ ├── linux
│ └── logo.png
│ ├── logback-dev.xml
│ ├── logback-prd.xml
│ ├── mac
│ ├── dock_logo.png
│ ├── logo.png
│ └── mitm-tool.bin
│ └── windows
│ ├── logo.png
│ └── logo_xp.png
├── pom.xml
└── runner
├── .gitignore
├── pom.xml
└── src
└── main
├── deploy
└── package
│ ├── linux
│ └── Proxyee Down.png
│ ├── macosx
│ └── Proxyee Down.icns
│ └── windows
│ └── Proxyee Down.ico
└── java
└── org
└── pdown
└── gui
└── Runner.java
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 | ### 问题描述(必要)
6 | ### 版本号(必要)
7 | ### 操作系统(必要)
8 | ### 相关截图
9 | ### 相关日志
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | target/
4 | log/
5 | !.mvn/wrapper/maven-wrapper.jar
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 |
15 | ### IntelliJ IDEA ###
16 | .idea
17 | *.iws
18 | *.iml
19 | *.ipr
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 续作出炉!!!
2 |
3 | 新项目使用`golang`+`flutter`开发,支持所有平台的下载器,地址:https://github.com/GopeedLab/gopeed
4 |
5 | ## 暂停维护此项目
6 | 首先感谢大家支持和反馈才使得proxyee-down能一直迭代到现在的版本,但由于本人精力有限,宣布暂时停止此项目的维护,并且关闭**issue**模块。
7 |
8 | 其次因为`JAVA`不太适合做客户端开发,打包后体积太大且内存占用太高,本人计划在空余时间用`GO`来重写一遍,目标是打造一个`体积小`、`跨平台`、`内存低`、`可扩展`、`免费`的下载器。
9 |
10 | 
11 |
12 | # [Proxyee Down](https://pdown.org)
13 | [](https://github.com/monkeyWie)
14 | [](https://github.com/proxyee-down-org/proxyee-down/graphs/contributors)
15 | [](https://github.com/proxyee-down-org/proxyee-down/stargazers)
16 | [](https://github.com/proxyee-down-org/proxyee-down/fork)
17 | [](https://github.com/proxyee-down-org/proxyee-down/blob/master/LICENSE)
18 |
19 | > Proxyee Down 是一款开源的免费 HTTP 高速下载器,底层使用`netty`开发,支持自定义 HTTP 请求下载且支持扩展功能,可以通过安装扩展实现特殊的下载需求。
20 |
21 | ## 使用教程
22 |
23 | [点击查看教程](https://github.com/proxyee-down-org/proxyee-down/wiki/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B)
24 |
25 | ## 交流群
26 |
27 | 1 群**11352304**、2 群**20236964**、3 群**20233754**、4 群**737991056**
28 |
29 | ## 开发
30 |
31 | 本项目后端主要使用 `java` + `spring` + `boot` + `netty`,前端使用 `vue.js` + `iview`
32 |
33 | ### 环境
34 |   
35 |
36 | oracle jdk 1.8+或 openjfx(openjdk默认不包含javafx包)
37 |
38 | ### 编译
39 |
40 | ```
41 | git clone https://github.com/proxyee-down-org/proxyee-down.git
42 | cd proxyee-down/front
43 | #build html
44 | npm install
45 | npm run build
46 | cd ../main
47 | mvn clean package -Pprd
48 | ```
49 |
50 | ### 运行
51 | ```
52 | java -jar proxyee-down-main.jar
53 | ```
54 |
--------------------------------------------------------------------------------
/front/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ['plugin:vue/essential', 'eslint:recommended'],
7 | rules: {
8 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
9 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
10 | 'no-alert': process.env.NODE_ENV === 'production' ? 'error' : 'off',
11 | //强制使用单引号
12 | quotes: ['error', 'single'],
13 | //强制不使用分号结尾
14 | semi: ['error', 'never']
15 | },
16 | parserOptions: {
17 | parser: 'babel-eslint'
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/front/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 |
17 | *.suo
18 | *.ntvs*
19 | *.njsproj4
20 | *.sln
21 | *.sw*
22 |
23 | package-lock.json
--------------------------------------------------------------------------------
/front/.postcssrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
--------------------------------------------------------------------------------
/front/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "eslintIntegration": true,
3 | "singleQuote": true,
4 | "semi": false,
5 | "printWidth": 120
6 | }
7 |
--------------------------------------------------------------------------------
/front/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "vetur.format.defaultFormatter.html": "js-beautify-html",
3 | "vetur.format.defaultFormatterOptions": {
4 | "js-beautify-html": {
5 | "wrap_attributes": "force"
6 | }
7 | },
8 | "files.associations": {
9 | "*.vue": "vue"
10 | },
11 | "eslint.validate": [
12 | "javascript",
13 | "javascriptreact",
14 | {
15 | "language": "vue",
16 | "autoFix": true
17 | }
18 | ],
19 | "eslint.autoFixOnSave": true
20 | }
21 |
--------------------------------------------------------------------------------
/front/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/app'],
3 | plugins: ['jsx-v-model']
4 | }
5 |
--------------------------------------------------------------------------------
/front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proxyee-down",
3 | "version": "3.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "npm run serve",
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "axios": "^0.18.0",
13 | "iview": "^2.14.3",
14 | "numeral": "^2.0.6",
15 | "reconnecting-websocket": "^4.1.0",
16 | "vue": "^2.5.17",
17 | "vue-i18n": "^8.0.0",
18 | "vue-router": "^3.0.1",
19 | "vuex": "^3.0.1"
20 | },
21 | "devDependencies": {
22 | "@vue/cli-plugin-babel": "^3.0.1",
23 | "@vue/cli-plugin-eslint": "^3.0.1",
24 | "@vue/cli-service": "^3.0.1",
25 | "@vue/eslint-config-prettier": "^3.0.1",
26 | "babel-plugin-jsx-v-model": "^2.0.3",
27 | "iview-loader": "^1.2.1",
28 | "less": "^3.8.1",
29 | "less-loader": "^4.1.0",
30 | "lint-staged": "^6.0.0",
31 | "vue-template-compiler": "^2.5.17"
32 | },
33 | "browserslist": [
34 | "> 1%",
35 | "last 2 versions",
36 | "not ie <= 8"
37 | ],
38 | "gitHooks": {
39 | "pre-commit": "lint-staged"
40 | },
41 | "lint-staged": {
42 | "*.js": [
43 | "vue-cli-service lint",
44 | "git add"
45 | ],
46 | "*.vue": [
47 | "vue-cli-service lint",
48 | "git add"
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/a8f1709f1603ef0c92e4818261842cb4eb804f79/front/public/favicon.ico
--------------------------------------------------------------------------------
/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Proxyee Down
10 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/front/public/pay/alipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/a8f1709f1603ef0c92e4818261842cb4eb804f79/front/public/pay/alipay.png
--------------------------------------------------------------------------------
/front/public/pay/weipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/a8f1709f1603ef0c92e4818261842cb4eb804f79/front/public/pay/weipay.png
--------------------------------------------------------------------------------
/front/public/team_header/Black-Hole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/a8f1709f1603ef0c92e4818261842cb4eb804f79/front/public/team_header/Black-Hole.png
--------------------------------------------------------------------------------
/front/public/team_header/NISAL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/a8f1709f1603ef0c92e4818261842cb4eb804f79/front/public/team_header/NISAL.png
--------------------------------------------------------------------------------
/front/public/team_header/monkeyWie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/a8f1709f1603ef0c92e4818261842cb4eb804f79/front/public/team_header/monkeyWie.png
--------------------------------------------------------------------------------
/front/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 | {{ $t("nav.tasks") }}
11 |
12 |
13 |
14 |
15 |
16 | {{ $t("nav.extension") }}
17 |
18 |
19 |
20 |
21 |
22 | {{ $t("nav.setting") }}
23 |
24 |
25 |
26 |
27 |
28 | {{ $t("nav.about") }}
29 |
30 |
31 |
32 |
33 |
34 | {{ $t("nav.support") }}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
98 |
99 |
113 |
114 |
--------------------------------------------------------------------------------
/front/src/common/http.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import axios from 'axios'
3 |
4 | export default {
5 | build() {
6 | const client = axios.create()
7 | client.interceptors.request.use(
8 | config => {
9 | Vue.prototype.$Spin.show()
10 | return config
11 | },
12 | error => Promise.reject(error)
13 | )
14 | client.interceptors.response.use(
15 | response => {
16 | Vue.prototype.$Spin.hide()
17 | return response
18 | },
19 | error => {
20 | Vue.prototype.$Spin.hide()
21 | return Promise.reject(error)
22 | }
23 | )
24 | return client
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/front/src/common/native.js:
--------------------------------------------------------------------------------
1 | import http from './http'
2 | import axios from 'axios'
3 |
4 | const client = http.build()
5 | const clientNoSpin = axios.create()
6 |
7 | /**
8 | * 弹出原生文件选择框
9 | */
10 | export const showFileChooser = () => {
11 | return new Promise((resolve, reject) => {
12 | client
13 | .get('/native/fileChooser')
14 | .then(response => resolve(response.data))
15 | .catch(error => reject(error))
16 | })
17 | }
18 |
19 | /**
20 | * 弹出原生文件夹选择框
21 | */
22 | export const showDirChooser = () => {
23 | return new Promise((resolve, reject) => {
24 | client
25 | .get('/native/dirChooser')
26 | .then(response => resolve(response.data))
27 | .catch(error => reject(error))
28 | })
29 | }
30 |
31 | /**
32 | * 取应用初始化配置信息
33 | */
34 | export const getInitConfig = () => {
35 | return new Promise((resolve, reject) => {
36 | client
37 | .get('/native/getInitConfig')
38 | .then(response => resolve(response.data))
39 | .catch(error => reject(error))
40 | })
41 | }
42 |
43 | /**
44 | * 弹出系统资源管理器并选中指定文件
45 | * @param {string} path 文件路径
46 | */
47 | export const showFile = path => {
48 | return new Promise((resolve, reject) => {
49 | client
50 | .post('/native/showFile', { path: path })
51 | .then(response => resolve(response.data))
52 | .catch(error => reject(error))
53 | })
54 | }
55 |
56 | /**
57 | * 检查证书是否安装
58 | */
59 | export const checkCert = () => {
60 | return new Promise((resolve, reject) => {
61 | clientNoSpin
62 | .get('/native/checkCert')
63 | .then(response => resolve(response.data.status))
64 | .catch(error => reject(error))
65 | })
66 | }
67 |
68 | /**
69 | * 安装证书
70 | */
71 | export const installCert = () => {
72 | return new Promise((resolve, reject) => {
73 | client
74 | .get('/native/installCert')
75 | .then(response => resolve(response.data.status))
76 | .catch(error => reject(error))
77 | })
78 | }
79 |
80 | /**
81 | * 取设置的代理模式
82 | */
83 | export const getProxyMode = () => {
84 | return new Promise((resolve, reject) => {
85 | clientNoSpin
86 | .get('/native/getProxyMode')
87 | .then(response => resolve(response.data.mode))
88 | .catch(error => reject(error))
89 | })
90 | }
91 |
92 | /**
93 | * 修改代理模式
94 | * @param {number} mode 0.不接管系统代理 1.接管系统代理
95 | */
96 | export const changeProxyMode = mode => {
97 | return new Promise((resolve, reject) => {
98 | client
99 | .post('/native/changeProxyMode', { mode: mode })
100 | .then(response => resolve(response.data))
101 | .catch(error => reject(error))
102 | })
103 | }
104 |
105 | /**
106 | * 取本地已安装的扩展列表
107 | */
108 | export const getExtensions = () => {
109 | return new Promise((resolve, reject) => {
110 | clientNoSpin
111 | .get('/native/getExtensions')
112 | .then(response => resolve(response.data))
113 | .catch(error => reject(error))
114 | })
115 | }
116 |
117 | /**
118 | * 安装指定扩展
119 | * @param {object} data 扩展相关信息
120 | */
121 | export const installExtension = data => {
122 | return new Promise((resolve, reject) => {
123 | clientNoSpin
124 | .post('/native/installExtension', data)
125 | .then(response => resolve(response.data))
126 | .catch(error => reject(error))
127 | })
128 | }
129 |
130 | /**
131 | * 更新指定扩展
132 | * @param {object} data 扩展相关信息
133 | */
134 | export const updateExtension = data => {
135 | return new Promise((resolve, reject) => {
136 | clientNoSpin
137 | .post('/native/updateExtension', data)
138 | .then(response => resolve(response.data))
139 | .catch(error => reject(error))
140 | })
141 | }
142 |
143 | /**
144 | * 安装本地扩展
145 | * @param {string} path 扩展所在目录
146 | */
147 | export const installLocalExtension = path => {
148 | return new Promise((resolve, reject) => {
149 | clientNoSpin
150 | .post('/native/installLocalExtension', { path: path })
151 | .then(response => resolve(response.data.data))
152 | .catch(error => reject(error))
153 | })
154 | }
155 |
156 | /**
157 | * 卸载扩展
158 | * @param {string} path 扩展所在目录
159 | * @param {boolean} isLocal 是否本地加载的扩展
160 | */
161 | export const uninstallExtension = (path, local) => {
162 | return new Promise((resolve, reject) => {
163 | client
164 | .post('/native/uninstallExtension', { path, local })
165 | .then(response => resolve(response.data))
166 | .catch(error => reject(error))
167 | })
168 | }
169 |
170 | /**
171 | * 启用或禁用扩展
172 | * @param {object} data
173 | */
174 | export const toggleExtension = data => {
175 | return new Promise((resolve, reject) => {
176 | client
177 | .post('/native/toggleExtension', data)
178 | .then(response => resolve(response.data))
179 | .catch(error => reject(error))
180 | })
181 | }
182 |
183 | /**
184 | * 保存指定扩展的设置
185 | * @param {String} path 扩展路径
186 | * @param {object} setting 扩展设置信息
187 | */
188 | export const updateExtensionSetting = (path, setting) => {
189 | return new Promise((resolve, reject) => {
190 | client
191 | .post('/native/updateExtensionSetting', { path, setting })
192 | .then(response => resolve(response.data))
193 | .catch(error => reject(error))
194 | })
195 | }
196 |
197 | /**
198 | * 打开浏览器并访问指定url
199 | * @param {object} data
200 | */
201 | export const openUrl = url => {
202 | if (window.navigator.userAgent.indexOf('JavaFX') !== -1) {
203 | clientNoSpin.post('/native/openUrl', { url: encodeURIComponent(url) })
204 | } else {
205 | window.open(url)
206 | }
207 | }
208 |
209 | /**
210 | * 在解析任务时触发
211 | * @param {object} request
212 | */
213 | export const onResolve = request => {
214 | return new Promise((resolve, reject) => {
215 | clientNoSpin
216 | .post('/native/onResolve', request)
217 | .then(response => resolve(response.data))
218 | .catch(error => reject(error))
219 | })
220 | }
221 |
222 | /**
223 | * 更新软件
224 | * @param {string} path 更新包下载地址
225 | */
226 | export const doUpdate = path => {
227 | return new Promise((resolve, reject) => {
228 | clientNoSpin
229 | .post('/native/doUpdate', { path: path })
230 | .then(response => resolve(response.data))
231 | .catch(error => reject(error))
232 | })
233 | }
234 |
235 | /**
236 | * 更新软件进度获取
237 | */
238 | export const getUpdateProgress = () => {
239 | return new Promise((resolve, reject) => {
240 | clientNoSpin
241 | .get('/native/getUpdateProgress')
242 | .then(response => resolve(response.data))
243 | .catch(error => reject(error))
244 | })
245 | }
246 |
247 | /**
248 | * 重启软件
249 | */
250 | export const doRestart = () => {
251 | return new Promise((resolve, reject) => {
252 | client
253 | .get('/native/doRestart')
254 | .then(response => resolve(response.data))
255 | .catch(error => reject(error))
256 | })
257 | }
258 |
259 | /**
260 | * 取软件设置信息
261 | */
262 | export const getConfig = () => {
263 | return new Promise((resolve, reject) => {
264 | clientNoSpin
265 | .get('/native/getConfig')
266 | .then(response => resolve(response.data))
267 | .catch(error => reject(error))
268 | })
269 | }
270 |
271 | /**
272 | * 保存软件设置
273 | * @param {object} config
274 | */
275 | export const setConfig = config => {
276 | return new Promise((resolve, reject) => {
277 | clientNoSpin
278 | .put('/native/setConfig', config)
279 | .then(response => resolve(response))
280 | .catch(error => reject(error))
281 | })
282 | }
283 |
284 | /**
285 | * 复制数据到系统剪贴板
286 | * @param {object} data
287 | */
288 | export const copy = data => {
289 | return new Promise((resolve, reject) => {
290 | clientNoSpin
291 | .put('/native/copy', data)
292 | .then(response => resolve(response))
293 | .catch(error => reject(error))
294 | })
295 | }
296 |
--------------------------------------------------------------------------------
/front/src/components/ExtensionSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
41 |
42 |
--------------------------------------------------------------------------------
/front/src/components/FileChoose/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
13 |
14 |
53 |
54 |
67 |
--------------------------------------------------------------------------------
/front/src/components/Table/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
{{ $t("tasks.fileName") }}
13 |
{{ $t("tasks.fileSize") }}
14 |
{{ $t("tasks.taskProgress") }}
15 |
{{ $t("tasks.downloadSpeed") }}
16 |
{{ $t("tasks.status") }}
17 |
{{ $t("tasks.operate") }}
18 |
19 |
20 |
21 |
22 |
24 |
27 |
29 |
30 |
31 |
33 |
34 |
{{ task.response.fileName }}
35 |
{{ task.response.totalSize?$numeral(task.response.totalSize).format('0.00 ib'):$t('tasks.unknowLeft') }}
36 |
{{ calcProgress(task) }}
37 |
{{ $numeral(task.info.speed).format('0.00 ib') }}/S
38 |
{{ calcStatus(task) }}
39 |
40 |
45 |
50 |
54 |
58 |
63 |
66 |
68 |
69 | {{ $t('tasks.url') }}:
70 | {{ task.request.url }}
71 |
72 |
73 | {{ $t('tasks.fileName') }}:
74 | {{ task.response.fileName }}
75 |
76 |
77 | {{ $t('tasks.filePath') }}:
78 | {{ task.config.filePath }}
79 |
80 |
81 | {{ $t('tasks.fileSize') }}:
82 | {{ $numeral(task.response.totalSize).format('0.00 ib') }}
83 |
84 |
85 | {{ $t('tasks.connections') }}:
86 | {{ task.config.connections }}
87 |
88 |
89 | {{ $t('tasks.downloadSpeed') }}:
90 | {{ $numeral(task.info.speed).format('0.00 ib') }}/S
91 |
92 |
93 | {{ $t('tasks.status') }}:
94 | {{ calcStatus(task) }}
95 |
96 |
97 | {{ $t("tasks.createTime") }}:
98 | {{ new Date(task.info.startTime).format('yyyy-MM-dd hh:mm:ss') }}
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
202 |
203 |
298 |
299 |
308 |
309 |
--------------------------------------------------------------------------------
/front/src/components/Task/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
57 |
58 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/front/src/components/Task/Resolve.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
157 |
158 |
178 |
--------------------------------------------------------------------------------
/front/src/i18n/en-US.js:
--------------------------------------------------------------------------------
1 | export default {
2 | nav: {
3 | tasks: 'Tasks',
4 | extension: 'Extensions',
5 | setting: 'Settings',
6 | about: 'About',
7 | support: 'Support Us'
8 | },
9 | tip: {
10 | tip: 'Hint',
11 | ok: 'OK',
12 | cancel: 'Cancel',
13 | notNull: 'Cannot be empty',
14 | fmtErr: 'Incorrect format',
15 | choose: 'Choose',
16 | save: 'Save',
17 | refresh: 'Refresh',
18 | copySucc: 'Copied successfully',
19 | copyFail: 'Copy failed',
20 | saveSucc: 'Save successfully',
21 | saveFail: 'Save failed'
22 | },
23 | tasks: {
24 | createTask: 'New Task',
25 | continueDownloading: 'Resume',
26 | pauseDownloads: 'Pause',
27 | deleteTask: 'Delete Task',
28 | deleteTaskTip: 'Delete both task and file',
29 | revealInFolder: 'Reveal in download folder',
30 | method: 'Method',
31 | url: 'URL',
32 | fileName: 'Name',
33 | fileSize: 'Size',
34 | connections: 'Connections',
35 | filePath: 'Path',
36 | status: 'Status',
37 | operate: 'Actions',
38 | downloadAddress: 'Address',
39 | wait: 'Waiting',
40 | unknowLeft: 'Unknown',
41 | downloadSpeed: 'Speed',
42 | createTime: 'Created',
43 | taskProgress: 'Progress',
44 | statusPause: 'Pause',
45 | statusFail: 'Failed',
46 | statusDone: 'Done',
47 | option: 'Options',
48 | head: 'Header',
49 | body: 'Body',
50 | detail: 'Details',
51 | checkSameTask: 'The same task already exists. Refresh the task?',
52 | sameTaskList: 'Task List',
53 | sameTaskPlaceholder: 'Please select the task to refresh',
54 | running: 'Downloading',
55 | waiting: 'Waiting',
56 | done: 'Done'
57 | },
58 | extension: {
59 | conditions: 'Notes',
60 | conditionsContent:
61 | 'When using the extension for the first time, you must install a CA certificate randomly generated by Proxyee Down. Click Install below and follow the instructions. If a Proxyee Down CA certificate has been installed, you will be prompted to delete the old CA certificate.',
62 | install: 'Install',
63 | globalProxy: 'Global Proxy',
64 | proxyTip: 'View instructions',
65 | copyPac: 'Copy PAC URL',
66 | title: 'Title',
67 | description: 'Description',
68 | currVersion: 'Current Version',
69 | newVersion: 'Latest Version',
70 | installStatus: 'Status',
71 | installStatusTrue: 'ON',
72 | installStatusFalse: 'OFF',
73 | action: 'Actions',
74 | actionUpdate: 'Update',
75 | actionInstall: 'Install',
76 | uninstall: 'Uninstall',
77 | uninstallTip: 'Do you want to uninstall this extension?',
78 | actionDetail: 'Details',
79 | switch: 'ON/OFF',
80 | downloadingTip: 'Downloading...[servers(',
81 | downloadOk: 'Downloaded successfully',
82 | downloadErr: 'Download failed',
83 | downloadErrTip: 'Automatically switch servers',
84 | extCenter: 'Extension center',
85 | installLocalExt: 'Install local extension',
86 | installOk: 'Installed successfully',
87 | installErr: 'Installation failed, please check the manifest.json file',
88 | setting: 'Setting'
89 | },
90 | setting: {
91 | downSetting: 'Download settings',
92 | path: 'Path',
93 | pathTip: 'Default download path',
94 | connections: 'Connections',
95 | connectionsTip: 'Default download connections',
96 | taskLimit: 'Simultaneous download tasks',
97 | taskSpeedLimit: 'Single task speed limit',
98 | globalSpeedLimit: 'Global speed limit',
99 | speedLimitTip: '0 for unlimited',
100 | appSetting: 'System settings',
101 | language: 'Language',
102 | uiMode: 'UI mode',
103 | uiModeWindows: 'Windows',
104 | uiModeBrowser: 'Browser',
105 | autoOpen: 'Popup at startup',
106 | checkUpdate: 'Check for update',
107 | checkUpdateWeek: 'Every week',
108 | checkUpdateStartup: 'Every startup',
109 | checkUpdateNever: 'Never',
110 | secondProxy: {
111 | secondProxy: 'Second proxy',
112 | tip: 'Configure the second (pre-proxy) proxy server for the downloader',
113 | type: 'Type',
114 | host: 'Host',
115 | port: 'Port',
116 | user: 'Username',
117 | pwd: 'password'
118 | }
119 | },
120 | about: {
121 | project: {
122 | title: 'Project',
123 | content:
124 | 'Proxyee-Down is an open source, free software based on the software\'s high-speed download kernel and extensions to easily and quickly download the required resources.',
125 | githubAddress: 'Project homepage: ',
126 | official: 'Official website: ',
127 | community: 'Official community: ',
128 | tutorial: 'Tutorials: ',
129 | feedback: 'Feedback: ',
130 | currentVersion: 'Current Version: ',
131 | checkUpdate: 'Check update:',
132 | noNewVersion: 'Already the latest version'
133 | },
134 | team: {
135 | title: 'Team'
136 | }
137 | },
138 | update: {
139 | checkNew: 'New version available',
140 | version: 'Version',
141 | changeLog: 'Changelog',
142 | update: 'Update',
143 | done: 'Update completed',
144 | restart: 'Restart Proxyee Down?',
145 | error: 'Update failed, please check the network or manually download the update package'
146 | },
147 | alert: {
148 | refused: 'Program exception: Connection refused',
149 | timeout: 'Program exception: Connection timeout',
150 | error: 'Program error',
151 | notFound: 'Task not found',
152 | '/tasks': {
153 | post: {
154 | 4000: 'Params parse error',
155 | 4001: 'Request is empty',
156 | 4002: 'Request URL is empty',
157 | 4003: 'File save path is empty',
158 | 4004: 'Failed to create folder',
159 | 4005: 'No write permission',
160 | 4006: 'Not enough disk space',
161 | 4007: 'File already exists'
162 | }
163 | }
164 | },
165 | '/util/resolve': {
166 | put: {
167 | 4000: 'Params parse error',
168 | 4001: 'Request URL is empty',
169 | 4002: 'Response status code exception',
170 | 4003: 'Request timeout'
171 | }
172 | },
173 | '/config': {
174 | put: {
175 | 4000: 'Params parse error'
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/front/src/i18n/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | nav: {
3 | tasks: '任务管理',
4 | extension: '扩展管理',
5 | setting: '软件设置',
6 | about: '关于项目',
7 | support: '支持我们'
8 | },
9 | tip: {
10 | tip: '提示',
11 | ok: '确定',
12 | cancel: '取消',
13 | notNull: '不能为空',
14 | fmtErr: '格式不正确',
15 | choose: '选择',
16 | save: '保存',
17 | refresh: '刷新',
18 | copySucc: '复制成功',
19 | copyFail: '复制失败',
20 | saveSucc: '保存成功',
21 | saveFail: '保存失败'
22 | },
23 | tasks: {
24 | createTask: '创建任务',
25 | continueDownloading: '继续下载',
26 | pauseDownloads: '暂停下载',
27 | deleteTask: '删除任务',
28 | deleteTaskTip: '是否删除任务和文件?',
29 | revealInFolder: '打开下载目录',
30 | method: '方法',
31 | url: '链接',
32 | fileName: '文件名',
33 | fileSize: '大小',
34 | connections: '连接数',
35 | filePath: '路径',
36 | status: '状态',
37 | operate: '操作',
38 | downloadAddress: '下载地址',
39 | downloadSpeed: '下载速度',
40 | createTime: '开始时间',
41 | taskProgress: '任务进度',
42 | wait: '待下载',
43 | unknowLeft: '未知',
44 | statusPause: '暂停',
45 | statusFail: '失败',
46 | statusDone: '完成',
47 | option: '附加',
48 | head: '请求头',
49 | body: '请求体',
50 | detail: '下载详情',
51 | checkSameTask: '检测到可能相同的下载任务,是否选择任务进行刷新?',
52 | sameTaskList: '任务列表',
53 | sameTaskPlaceholder: '请选择要刷新的任务',
54 | running: '进行中',
55 | waiting: '等待中',
56 | done: '已完成'
57 | },
58 | extension: {
59 | conditions: '使用须知',
60 | conditionsContent:
61 | '首次使用扩展模块时,必须安装由Proxyee Down随机生成的一个CA证书,点击下面的安装按钮并按系统的引导进行确认安装。(注意:程序会在安装前检测操作系统中是否有安装过证书,当检测到有安装的情况会提示删除对应的旧CA证书)',
62 | install: '安装',
63 | globalProxy: '全局代理',
64 | proxyTip: '点击查看说明',
65 | copyPac: '复制PAC链接',
66 | title: '名称',
67 | description: '描述',
68 | currVersion: '当前版本',
69 | newVersion: '最新版本',
70 | installStatus: '状态',
71 | installStatusTrue: '已安装',
72 | installStatusFalse: '未安装',
73 | action: '操作',
74 | actionUpdate: '更新',
75 | actionInstall: '安装',
76 | uninstall: '卸载',
77 | uninstallTip: '确定卸载此扩展吗?',
78 | actionDetail: '详情',
79 | switch: '开关',
80 | downloadingTip: '下载中...[服务器(',
81 | downloadOk: '下载成功',
82 | downloadErr: '下载失败',
83 | downloadErrTip: '自动切换服务器',
84 | extCenter: '扩展中心',
85 | installLocalExt: '加载本地扩展',
86 | installOk: '加载成功',
87 | installErr: '加载失败,请检查manifest.json文件',
88 | setting: '设置'
89 | },
90 | setting: {
91 | downSetting: '下载设置',
92 | path: '路径',
93 | pathTip: '默认下载路径',
94 | connections: '连接数',
95 | connectionsTip: '默认连接数',
96 | taskLimit: '同时下载任务数',
97 | taskSpeedLimit: '单任务限速',
98 | globalSpeedLimit: '全局限速',
99 | speedLimitTip: '0为不限速',
100 | appSetting: '系统设置',
101 | language: '语言',
102 | uiMode: 'UI模式',
103 | uiModeWindows: '窗口',
104 | uiModeBrowser: '浏览器',
105 | autoOpen: '启动弹窗',
106 | checkUpdate: '检查更新',
107 | checkUpdateWeek: '每周',
108 | checkUpdateStartup: '每次启动',
109 | checkUpdateNever: '从不',
110 | secondProxy: {
111 | secondProxy: '二级代理',
112 | tip: '配置下载器的二级(前置)代理服务器',
113 | type: '类型',
114 | host: '服务器',
115 | port: '端口',
116 | user: '用户名',
117 | pwd: '密码'
118 | }
119 | },
120 | about: {
121 | project: {
122 | title: '项目',
123 | content: 'Proxyee Down是一款开源的免费软件,基于本软件的高速下载内核和扩展,可以方便并快速的下载所需资源。',
124 | githubAddress: '项目主页:',
125 | official: '官方网站:',
126 | community: '官方社区:',
127 | tutorial: '使用教程:',
128 | feedback: '问题反馈:',
129 | currentVersion: '当前版本:',
130 | checkUpdate: '检查更新:',
131 | noNewVersion: '已经是最新版本'
132 | },
133 | team: {
134 | title: '团队'
135 | }
136 | },
137 | update: {
138 | checkNew: '检测到新版本',
139 | version: '版本号',
140 | changeLog: '更新内容',
141 | update: '更新',
142 | done: '更新完毕',
143 | restart: '是否重新启动?',
144 | error: '更新失败,请检查网络或手动下载更新包'
145 | },
146 | alert: {
147 | refused: '程序异常,拒绝访问',
148 | timeout: '程序异常,连接超时',
149 | error: '程序出错',
150 | notFound: '任务不存在',
151 | '/tasks': {
152 | post: {
153 | 4000: '参数解析错误',
154 | 4001: '请求对象不能为空',
155 | 4002: '请求地址不能为空',
156 | 4003: '文件保存路径不能为空',
157 | 4004: '创建文件夹失败',
158 | 4005: '无写入权限',
159 | 4006: '磁盘空间不足',
160 | 4007: '文件已存在'
161 | }
162 | },
163 | '/util/resolve': {
164 | put: {
165 | 4000: '参数解析错误',
166 | 4001: '请求地址不能为空',
167 | 4002: '响应状态码异常',
168 | 4003: '请求超时'
169 | }
170 | },
171 | '/config': {
172 | put: {
173 | 4000: '参数解析错误'
174 | }
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/front/src/i18n/zh-TW.js:
--------------------------------------------------------------------------------
1 | export default {
2 | nav: {
3 | tasks: '任務管理',
4 | extension: '擴充管理',
5 | setting: '軟體設定',
6 | about: '關於專案',
7 | support: '支持我們'
8 | },
9 | tip: {
10 | tip: '提示',
11 | ok: '確定',
12 | cancel: '取消',
13 | notNull: '不能為空',
14 | fmtErr: '格式不正確',
15 | choose: '選擇',
16 | save: '儲存',
17 | refresh: '刷新',
18 | copySucc: '複製成功',
19 | copyFail: '複製失敗',
20 | saveSucc: '保存成功',
21 | saveFail: '保存失敗'
22 | },
23 | tasks: {
24 | createTask: '建立任務',
25 | continueDownloading: '繼續下載',
26 | pauseDownloads: '暫停下載',
27 | deleteTask: '刪除任務',
28 | deleteTaskTip: '是否刪除任務和檔案?',
29 | revealInFolder: '打開下載目錄',
30 | method: '方法',
31 | url: '連結',
32 | fileName: '名稱',
33 | fileSize: '大小',
34 | connections: '連線數',
35 | filePath: '路徑',
36 | status: '狀態',
37 | operate: '操作',
38 | downloadAddress: '下載位址',
39 | downloadSpeed: '下載速度',
40 | createTime: '開始時間',
41 | taskProgress: '任務進度',
42 | wait: '待下載',
43 | unknowLeft: '不詳',
44 | statusPause: '暫停',
45 | statusFail: '失敗',
46 | statusDone: '完成',
47 | option: '附加',
48 | head: '要求標頭',
49 | body: '要求主體',
50 | detail: '下載細節',
51 | checkSameTask: '偵測到可能相同的下載任務,是否選擇任務進行更新?',
52 | sameTaskList: '任務清單',
53 | sameTaskPlaceholder: '請選擇要更新的任務',
54 | running: '進行中',
55 | waiting: '等待中',
56 | done: '已完成'
57 | },
58 | extension: {
59 | conditions: '使用須知',
60 | conditionsContent:
61 | '首次使用擴充模組時,必須安裝由 Proxyee Down 隨機產生的一個 CA 憑證,點選下方的安裝按鈕並依系統的引導進行確認安裝。(注意:程式會在安裝前偵測作業系統中是否有安裝過憑證,當偵測到有安裝的情況會提示刪除對應的舊 CA 憑證)',
62 | install: '安裝',
63 | globalProxy: '全域代理',
64 | proxyTip: '點選檢視說明',
65 | copyPac: '複製 PAC 連結',
66 | title: '名稱',
67 | description: '描述',
68 | currVersion: '目前版本',
69 | newVersion: '最新版本',
70 | installStatus: '狀態',
71 | installStatusTrue: '已安装',
72 | installStatusFalse: '未安装',
73 | action: '操作',
74 | actionUpdate: '更新',
75 | actionInstall: '安裝',
76 | actionDetail: '細節',
77 | uninstall: '卸載',
78 | uninstallTip: '確定卸載此擴展嗎?',
79 | switch: '開關',
80 | downloadingTip: '下載中...[伺服器(',
81 | downloadOk: '下載成功',
82 | downloadErr: '下載失敗',
83 | downloadErrTip: '自動切換伺服器',
84 | extCenter: '擴充中心',
85 | installLocalExt: '加載本地擴充',
86 | installOk: '加載成功',
87 | installErr: '加載失敗,請檢查manifest.json文件',
88 | setting: '預設'
89 | },
90 | setting: {
91 | downSetting: '下載設定',
92 | path: '路徑',
93 | pathTip: '預設下載路徑',
94 | connections: '連線數',
95 | connectionsTip: '預設連線數',
96 | taskLimit: '同時下載任務數',
97 | taskSpeedLimit: '單任務限速',
98 | globalSpeedLimit: '全域限速',
99 | speedLimitTip: '0為不限速',
100 | appSetting: '系統設定',
101 | language: '語言',
102 | uiMode: 'UI 模式',
103 | uiModeWindows: '視窗',
104 | uiModeBrowser: '瀏覽器',
105 | autoOpen: '啟動彈窗',
106 | checkUpdate: '檢查更新',
107 | checkUpdateWeek: '每週',
108 | checkUpdateStartup: '每次啟動',
109 | checkUpdateNever: '從不',
110 | secondProxy: {
111 | secondProxy: '二級代理',
112 | tip: '配置下載器的二級(前置)代理服務器',
113 | type: '類型',
114 | host: '服務器',
115 | port: '端口',
116 | user: '用戶名',
117 | pwd: '密碼'
118 | }
119 | },
120 | about: {
121 | project: {
122 | title: '項目',
123 | content: 'Proxyee Down 是一款開源的免費軟體,基於本軟體的高速下載核心和擴充套件,可以方便並快速的下載所需資源。',
124 | githubAddress: '項目首頁:',
125 | official: '官方網站:',
126 | community: '官方社區:',
127 | tutorial: '使用教學:',
128 | feedback: '問題回報:',
129 | currentVersion: '目前版本:',
130 | checkUpdate: '檢查更新:',
131 | noNewVersion: '已經是最新版本'
132 | },
133 | team: {
134 | title: '團隊'
135 | }
136 | },
137 | update: {
138 | checkNew: '偵測到新版本',
139 | version: '版本號',
140 | changeLog: '更新內容',
141 | update: '更新',
142 | done: '更新完畢',
143 | restart: '是否重新啟動?',
144 | error: '更新失敗,請檢查網絡或手動下載更新包'
145 | },
146 | alert: {
147 | refused: '程式異常,拒絕存取',
148 | timeout: '程式異常,連線逾時',
149 | error: '程式出錯',
150 | notFound: '任務不存在',
151 | '/tasks': {
152 | post: {
153 | 4000: '參數解析錯誤',
154 | 4001: '要求對象不能為空',
155 | 4002: '要求位址不能為空',
156 | 4003: '檔案儲存路徑不能為空',
157 | 4004: '建立資料夾失敗',
158 | 4005: '無寫入權限',
159 | 4006: '磁碟空間不足',
160 | 4007: '檔案已存在'
161 | }
162 | },
163 | '/util/resolve': {
164 | put: {
165 | 4000: '參數解析錯誤',
166 | 4001: '要求位址不能為空',
167 | 4002: '回應狀態碼異常',
168 | 4003: '請求超時'
169 | }
170 | },
171 | '/config': {
172 | put: {
173 | 4000: '參數解析錯誤'
174 | }
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/front/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | import iView from 'iview'
6 | import axios from 'axios'
7 | import VueI18n from 'vue-i18n'
8 | import numeral from 'numeral'
9 |
10 | import 'iview/dist/styles/iview.css'
11 | import en_US from 'iview/dist/locale/en-US'
12 | import zh_CN from 'iview/dist/locale/zh-CN'
13 | import zh_TW from 'iview/dist/locale/zh-TW'
14 | import http from './common/http'
15 | import { getInitConfig } from './common/native'
16 |
17 | Vue.use(VueI18n)
18 | Vue.use(iView)
19 |
20 | Vue.config.productionTip = false
21 |
22 | // Setting i18n
23 | const i18n = new VueI18n({
24 | locale: 'zh-CN',
25 | messages: {
26 | 'en-US': Object.assign(require('./i18n/en-US').default, en_US),
27 | 'zh-CN': Object.assign(require('./i18n/zh-CN').default, zh_CN),
28 | 'zh-TW': Object.assign(require('./i18n/zh-TW').default, zh_TW)
29 | }
30 | })
31 |
32 | Vue.prototype.$noSpinHttp = axios.create()
33 | Vue.prototype.$http = http.build()
34 | Vue.prototype.$http.interceptors.response.use(
35 | response => {
36 | return response
37 | },
38 | error => {
39 | if (!error.response) {
40 | Vue.prototype.$Message.error(i18n.t('alert.refused'))
41 | } else if (error.response.status == 400) {
42 | let i18nKey =
43 | 'alert["' +
44 | new URL(error.config.url).pathname +
45 | '"]' +
46 | '.' +
47 | error.config.method +
48 | '.' +
49 | error.response.data.code
50 | Vue.prototype.$Message.error(i18n.t(i18nKey))
51 | } else if (error.response.status == 404) {
52 | Vue.prototype.$Message.error(i18n.t('alert.notFound'))
53 | } else if (error.response.status == 504) {
54 | Vue.prototype.$Message.error(i18n.t('alert.timeout'))
55 | } else {
56 | Vue.prototype.$Message.error(i18n.t('alert.error'))
57 | }
58 | return Promise.reject(error)
59 | }
60 | )
61 |
62 | //去除字节大小格式化后的i字符
63 | const format = numeral.prototype.constructor.fn.format
64 | numeral.prototype.constructor.fn.format = function(fmt) {
65 | let result = format.call(this, fmt)
66 | if (/^.*ib$/.test(fmt)) {
67 | result = result.replace('i', '')
68 | }
69 | return result
70 | }
71 | Vue.prototype.$numeral = numeral
72 |
73 | Date.prototype.format = function(fmt) {
74 | var o = {
75 | 'M+': this.getMonth() + 1, // Month
76 | 'd+': this.getDate(), // Day
77 | 'h+': this.getHours(), // Hour
78 | 'm+': this.getMinutes(), // Minute
79 | 's+': this.getSeconds(), // Second
80 | 'q+': Math.floor((this.getMonth() + 3) / 3), // Quarter
81 | S: this.getMilliseconds() // Millisecond
82 | }
83 | if (/(y+)/.test(fmt)) {
84 | fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length))
85 | }
86 | for (var k in o) {
87 | if (new RegExp('(' + k + ')').test(fmt)) {
88 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
89 | }
90 | }
91 | return fmt
92 | }
93 |
94 | Promise.prototype.finally = function(callback) {
95 | let P = this.constructor
96 | return this.then(
97 | value => P.resolve(callback()).then(() => value),
98 | reason =>
99 | P.resolve(callback()).then(() => {
100 | throw reason
101 | })
102 | )
103 | }
104 |
105 | // Change the page according to the routing changes title
106 | router.beforeEach((to, from, next) => {
107 | if (to.meta.title) {
108 | document.title = `Proxyee Down-${to.meta.title}`
109 | }
110 | next()
111 | })
112 |
113 | // Get client configuration information
114 | getInitConfig()
115 | .then(result => {
116 | Vue.prototype.$config = result
117 | // Set default language
118 | i18n.locale = result.locale
119 | })
120 | .catch(() => {
121 | Vue.prototype.$config = {}
122 | })
123 | .finally(() => {
124 | new Vue({
125 | router,
126 | store,
127 | i18n,
128 | data() {
129 | return {
130 | badges: { tasks: 0, extension: 0, setting: 0, about: 0, support: 0 }
131 | }
132 | },
133 | render: h => h(App)
134 | }).$mount('#app')
135 | })
136 |
--------------------------------------------------------------------------------
/front/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Tasks from './views/Tasks.vue'
4 | import Extension from './views/Extension.vue'
5 | import Setting from './views/Setting.vue'
6 | import About from './views/About.vue'
7 | import Support from './views/Support.vue'
8 |
9 | Vue.use(Router)
10 |
11 | export default new Router({
12 | routes: [
13 | {
14 | path: '/',
15 | redirect: '/tasks'
16 | },
17 | {
18 | path: '/tasks',
19 | name: 'tasks',
20 | component: Tasks
21 | },
22 | {
23 | path: '/extension',
24 | name: 'extension',
25 | component: Extension
26 | },
27 | {
28 | path: '/setting',
29 | name: 'setting',
30 | component: Setting
31 | },
32 | {
33 | path: '/about',
34 | name: 'About',
35 | component: About
36 | },
37 | {
38 | path: '/support',
39 | name: 'Support',
40 | component: Support
41 | }
42 | ]
43 | })
44 |
--------------------------------------------------------------------------------
/front/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | export default new Vuex.Store({
7 | state: {},
8 | mutations: {},
9 | actions: {}
10 | })
11 |
--------------------------------------------------------------------------------
/front/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t("about.project.title") }}
5 |
54 |
55 |
56 |
57 |
58 |
59 | {{ $t("about.team.title") }}
60 |
61 |
62 |

64 |
monkeyWie
65 |
66 |
67 |
68 |
69 |

71 |
Black-Hole
72 |
73 |
74 |
75 |
76 |

78 |
NISAL
79 |
80 |
81 |
82 |
83 |
84 |
86 | {{ $t('update.version') }}:
87 | {{ versionInfo.version }}
88 |
89 |
90 | {{ $t('update.changeLog') }}:
91 |
93 |
94 |
95 |
97 |
98 |
99 |
100 |
102 | {{ $t('update.restart') }}
103 |
104 |
105 |
107 |
108 |
109 |
110 |
114 |
116 | {{ updateInfo.progress.toFixed(2) }}%
117 | {{ $numeral(updateInfo.speed).format('0.00 ib') }}/S
118 |
119 |
120 |
121 |
122 |
123 |
219 |
272 |
273 |
--------------------------------------------------------------------------------
/front/src/views/Support.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 如果觉得本软件不错的话,可以通过下面的二维码打赏作者,让作者有动力持续更新版本和修复BUG。
5 |
6 |

7 |
支付宝
8 |
9 |
10 |

11 |
微信
12 |
13 |
14 |
16 | 另外作者在这里推荐些正版软件,有购买意向的话可以点进去看一看,你们每一次点击也是对作者的支持与鼓励。
17 |
20 |
22 |
23 | {{soft.title}}
24 |
25 |
26 |
27 |
28 |
29 |
49 |
50 |
72 |
--------------------------------------------------------------------------------
/front/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | productionSourceMap: true, // Production environment does not generate source-map
3 | css: {
4 | sourceMap: false // CSS does not generate source-map
5 | },
6 | outputDir: '../main/src/main/resources/http',
7 | devServer: {
8 | proxy: {
9 | '/native': {
10 | target: 'http://127.0.0.1:7478',
11 | changeOrigin: true
12 | },
13 | '/pac': {
14 | target: 'http://127.0.0.1:7478',
15 | changeOrigin: true
16 | },
17 | '/ws': {
18 | target: 'http://127.0.0.1:7478',
19 | ws: true,
20 | }
21 | }
22 | },
23 | chainWebpack: config => {
24 | config.module
25 | .rule('vue')
26 | .test(/\.vue$/)
27 | .use('iview-loader')
28 | .loader('iview-loader')
29 | .options({
30 | prefix: true
31 | })
32 | }
33 | }
--------------------------------------------------------------------------------
/main/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | src/main/resources/http
4 |
5 | ### STS ###
6 | .apt_generated
7 | .classpath
8 | .factorypath
9 | .project
10 | .settings
11 | .springBeans
12 |
13 | ### IntelliJ IDEA ###
14 | .idea
15 | *.iws
16 | *.iml
17 | *.ipr
--------------------------------------------------------------------------------
/main/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | org.pdown.gui
7 | proxyee-down
8 | 3.0
9 |
10 |
11 | org.pdown.gui
12 | main
13 | jar
14 |
15 |
16 |
17 | snapshots-repo
18 | https://oss.sonatype.org/content/repositories/snapshots
19 |
20 | false
21 |
22 |
23 | true
24 |
25 |
26 |
27 |
28 |
29 |
30 | dev
31 |
32 | dev
33 |
34 |
35 | true
36 |
37 |
38 |
39 | prd
40 |
41 | prd
42 |
43 |
44 |
45 |
46 |
47 |
48 | org.pdown
49 | rest
50 | 1.0.2-SNAPSHOT
51 |
52 |
53 | com.github.monkeywie
54 | proxyee
55 | 1.0.4
56 |
57 |
58 | net.java.dev.jna
59 | jna
60 | 4.5.1
61 |
62 |
63 | org.yaml
64 | snakeyaml
65 | 1.19
66 |
67 |
68 |
69 |
70 |
71 |
72 | true
73 | src/main/resources
74 |
75 | application.yml
76 | application-dev.yml
77 | application-prd.yml
78 | logback-dev.xml
79 | logback-prd.xml
80 |
81 |
82 |
83 | true
84 | src/main/resources
85 |
86 | logback-${environment}.xml
87 | application-${environment}.yml
88 | application.yml
89 |
90 |
91 |
92 | proxyee-down-main
93 |
94 |
95 | org.apache.maven.plugins
96 | maven-resources-plugin
97 |
98 |
99 | bin
100 | css
101 | js
102 | eot
103 | woff
104 | ttf
105 | ico
106 | html
107 | svg
108 |
109 |
110 |
111 |
112 | org.springframework.boot
113 | spring-boot-maven-plugin
114 | 2.0.2.RELEASE
115 |
116 | org.pdown.gui.DownApplication
117 |
118 |
119 |
120 |
121 | repackage
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/com/Browser.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.com;
2 |
3 | import javafx.geometry.HPos;
4 | import javafx.geometry.VPos;
5 | import javafx.scene.control.ContextMenu;
6 | import javafx.scene.input.Clipboard;
7 | import javafx.scene.input.ClipboardContent;
8 | import javafx.scene.input.DataFormat;
9 | import javafx.scene.input.MouseButton;
10 | import javafx.scene.layout.Region;
11 | import javafx.scene.web.WebEngine;
12 | import javafx.scene.web.WebView;
13 | import org.pdown.gui.util.I18nUtil;
14 |
15 | public class Browser extends Region {
16 |
17 | final WebView webView = new WebView();
18 | final WebEngine webEngine = webView.getEngine();
19 | private javafx.scene.control.MenuItem copy;
20 | private javafx.scene.control.MenuItem paste;
21 |
22 | public Browser() {
23 | getChildren().add(webView);
24 | webView.setContextMenuEnabled(false);
25 | //自定义webview右键菜单
26 | final Clipboard clipboard = Clipboard.getSystemClipboard();
27 | ContextMenu contextMenu = new ContextMenu();
28 | copy = new javafx.scene.control.MenuItem();
29 | copy.setOnAction(e -> {
30 | ClipboardContent content = new ClipboardContent();
31 | Object selection = webView.getEngine().executeScript("window.getSelection().toString()");
32 | if (selection != null) {
33 | content.putString(selection.toString());
34 | clipboard.setContent(content);
35 | }
36 | });
37 | paste = new javafx.scene.control.MenuItem();
38 | paste.setOnAction(e -> {
39 | Object content = clipboard.getContent(DataFormat.PLAIN_TEXT);
40 | if (content != null) {
41 | webView.getEngine().executeScript("if(document.activeElement.selectionStart>=0){"
42 | + "var value = document.activeElement.value;"
43 | + "if(!value){"
44 | + " document.activeElement.value='" + content + "';"
45 | + "}else{"
46 | + " document.activeElement.value=value.substring(0,document.activeElement.selectionStart)+'" + content + "'+value.substring(document.activeElement.selectionEnd);"
47 | + "}"
48 | + "var event = document.createEvent('Event');"
49 | + "event.initEvent('input', true, true);"
50 | + "document.activeElement.dispatchEvent(event);"
51 | + "}");
52 | }
53 | });
54 | refreshText();
55 | contextMenu.getItems().addAll(copy, paste);
56 | webView.setOnMousePressed(e -> {
57 | if (e.getButton() == MouseButton.SECONDARY) {
58 | contextMenu.show(webView, e.getScreenX(), e.getScreenY());
59 | } else {
60 | contextMenu.hide();
61 | }
62 | });
63 | }
64 |
65 | @Override
66 | protected void layoutChildren() {
67 | double w = getWidth();
68 | double h = getHeight();
69 | layoutInArea(webView, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
70 | }
71 |
72 | public void load(String url) {
73 | webEngine.load(url);
74 | }
75 |
76 | public boolean isLoad() {
77 | return webEngine.getLocation() != null;
78 | }
79 |
80 | public void refreshText() {
81 | copy.setText(I18nUtil.getMessage("gui.menu.copy"));
82 | paste.setText(I18nUtil.getMessage("gui.menu.paste"));
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/com/CheckboxMenuItemGroup.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.com;
2 |
3 | import java.awt.CheckboxMenuItem;
4 | import java.awt.event.ItemEvent;
5 | import java.awt.event.ItemListener;
6 | import java.util.HashSet;
7 | import java.util.Set;
8 |
9 | public class CheckboxMenuItemGroup implements ItemListener {
10 |
11 | private Set items = new HashSet<>();
12 | private ItemListener itemListener;
13 |
14 | public void add(CheckboxMenuItem cbmi) {
15 | cbmi.addItemListener(this);
16 | cbmi.setState(false);
17 | items.add(cbmi);
18 | }
19 |
20 | public void addActionListener(ItemListener itemListener) {
21 | this.itemListener = itemListener;
22 | }
23 |
24 | @Override
25 | public void itemStateChanged(ItemEvent e) {
26 | CheckboxMenuItem checkedItem = ((CheckboxMenuItem) e.getSource());
27 | if (e.getStateChange() == ItemEvent.SELECTED) {
28 | String selectedItemName = checkedItem.getName();
29 | for (CheckboxMenuItem item : items) {
30 | if (!item.getName().equals(selectedItemName)) {
31 | item.setState(false);
32 | }
33 | }
34 | if (itemListener != null) {
35 | itemListener.itemStateChanged(e);
36 | }
37 | } else {
38 | checkedItem.setState(true);
39 | }
40 | }
41 |
42 | public void selectItem(CheckboxMenuItem itemToSelect) {
43 | for (CheckboxMenuItem item : items) {
44 | item.setState(item == itemToSelect);
45 | }
46 | }
47 |
48 | public CheckboxMenuItem getSelectedItem() {
49 | for (CheckboxMenuItem item : items) {
50 | if (item.getState()) {
51 | return item;
52 | }
53 | }
54 | return null;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/com/Components.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.com;
2 |
3 | import java.io.File;
4 | import javafx.geometry.Insets;
5 | import javafx.scene.Group;
6 | import javafx.scene.Scene;
7 | import javafx.scene.control.Alert;
8 | import javafx.scene.control.Alert.AlertType;
9 | import javafx.scene.control.ButtonBase;
10 | import javafx.scene.control.ButtonType;
11 | import javafx.scene.control.DialogPane;
12 | import javafx.stage.DirectoryChooser;
13 | import javafx.stage.FileChooser;
14 | import javafx.stage.Modality;
15 | import javafx.stage.Stage;
16 | import javafx.stage.StageStyle;
17 |
18 | public class Components {
19 |
20 | /**
21 | * 弹出提示窗,窗口置顶
22 | */
23 | public static void alert(String msg) {
24 | Alert alert = new Alert(AlertType.INFORMATION);
25 | // alert.setTitle("提示");
26 | alert.setHeaderText(null);
27 | alert.setContentText(msg);
28 |
29 | DialogPane root = alert.getDialogPane();
30 | Stage dialogStage = new Stage();
31 |
32 | for (ButtonType buttonType : root.getButtonTypes()) {
33 | ButtonBase button = (ButtonBase) root.lookupButton(buttonType);
34 | button.setOnAction(evt -> dialogStage.close());
35 | }
36 |
37 | root.getScene().setRoot(new Group());
38 | root.setPadding(new Insets(10, 0, 10, 0));
39 |
40 | Scene scene = new Scene(root);
41 | dialogStage.setScene(scene);
42 | dialogStage.initModality(Modality.APPLICATION_MODAL);
43 | dialogStage.setAlwaysOnTop(true);
44 | dialogStage.setResizable(false);
45 | dialogStage.showAndWait();
46 | }
47 |
48 | /**
49 | * 弹出文件选择框
50 | */
51 | public static File fileChooser() {
52 | Stage stage = buildBackgroundTopStage();
53 | FileChooser chooser = new FileChooser();
54 | chooser.setTitle("选择文件");
55 | File file = chooser.showOpenDialog(stage);
56 | stage.close();
57 | return file;
58 | }
59 |
60 | /**
61 | * 弹出文件夹选择框
62 | */
63 | public static File dirChooser() {
64 | Stage stage = buildBackgroundTopStage();
65 | DirectoryChooser chooser = new DirectoryChooser();
66 | chooser.setTitle("选择文件夹");
67 | File file = chooser.showDialog(stage);
68 | stage.close();
69 | return file;
70 | }
71 |
72 | private static Stage buildBackgroundTopStage() {
73 | Stage stage = new Stage();
74 | stage.setAlwaysOnTop(true);
75 | stage.setWidth(1);
76 | stage.setHeight(1);
77 | stage.initStyle(StageStyle.UNDECORATED);
78 | stage.show();
79 | return stage;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/content/PDownConfigContent.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.content;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import java.io.File;
5 | import java.nio.file.FileSystem;
6 | import java.nio.file.FileSystems;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Locale;
10 | import org.pdown.gui.entity.PDownConfigInfo;
11 | import org.pdown.rest.base.content.PersistenceContent;
12 | import org.pdown.rest.util.PathUtil;
13 |
14 | public class PDownConfigContent extends PersistenceContent {
15 |
16 | private static final PDownConfigContent INSTANCE = new PDownConfigContent();
17 |
18 | public static PDownConfigContent getInstance() {
19 | return INSTANCE;
20 | }
21 |
22 | @Override
23 | protected TypeReference type() {
24 | return new TypeReference() {
25 | };
26 | }
27 |
28 | @Override
29 | protected String savePath() {
30 | return PathUtil.ROOT_PATH + File.separator + "pdown.cfg";
31 | }
32 |
33 | @Override
34 | protected PDownConfigInfo defaultValue() {
35 | PDownConfigInfo pDownConfigInfo = new PDownConfigInfo();
36 | //取系统默认语言
37 | Locale defaultLocale = Locale.getDefault();
38 | pDownConfigInfo.setLocale(defaultLocale.getLanguage() + "-" + defaultLocale.getCountry());
39 | //插件文件服务器
40 | List extFileServers = new ArrayList<>();
41 | extFileServers.add("https://github.com/proxyee-down-org/proxyee-down-extension/raw/master");
42 | extFileServers.add("http://static.pdown.org/extensions");
43 | pDownConfigInfo.setExtFileServers(extFileServers);
44 | return pDownConfigInfo;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/entity/PDownConfigInfo.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.entity;
2 |
3 | import java.io.Serializable;
4 | import java.util.List;
5 | import org.pdown.core.proxy.ProxyConfig;
6 |
7 | public class PDownConfigInfo implements Serializable {
8 |
9 | private static final long serialVersionUID = 250452934883002540L;
10 | //客户端语言
11 | private String locale;
12 | //UI模式 0.浏览器模式 1.GUI模式
13 | private int uiMode = 1;
14 | //代理模式 0.不接管系统代理 1.由pdown接管系统代理
15 | private int proxyMode;
16 | //插件文件服务器(用于下载插件相关文件)
17 | private List extFileServers;
18 | //检测更新频率 0.从不 1.一周检查一次 2.每次打开检查
19 | private int updateCheckRate = 2;
20 | //启动时是否自动打开窗口
21 | private boolean autoOpen = true;
22 | //最后一次检查更新时间
23 | private long lastUpdateCheck;
24 | //前置代理
25 | private ProxyConfig proxyConfig;
26 |
27 | public String getLocale() {
28 | return locale;
29 | }
30 |
31 | public PDownConfigInfo setLocale(String locale) {
32 | this.locale = locale;
33 | return this;
34 | }
35 |
36 | public int getUiMode() {
37 | return uiMode;
38 | }
39 |
40 | public PDownConfigInfo setUiMode(int uiMode) {
41 | this.uiMode = uiMode;
42 | return this;
43 | }
44 |
45 | public int getProxyMode() {
46 | return proxyMode;
47 | }
48 |
49 | public PDownConfigInfo setProxyMode(int proxyMode) {
50 | this.proxyMode = proxyMode;
51 | return this;
52 | }
53 |
54 | public List getExtFileServers() {
55 | return extFileServers;
56 | }
57 |
58 | public PDownConfigInfo setExtFileServers(List extFileServers) {
59 | this.extFileServers = extFileServers;
60 | return this;
61 | }
62 |
63 | public int getUpdateCheckRate() {
64 | return updateCheckRate;
65 | }
66 |
67 | public PDownConfigInfo setUpdateCheckRate(int updateCheckRate) {
68 | this.updateCheckRate = updateCheckRate;
69 | return this;
70 | }
71 |
72 | public long getLastUpdateCheck() {
73 | return lastUpdateCheck;
74 | }
75 |
76 | public PDownConfigInfo setLastUpdateCheck(long lastUpdateCheck) {
77 | this.lastUpdateCheck = lastUpdateCheck;
78 | return this;
79 | }
80 |
81 | public ProxyConfig getProxyConfig() {
82 | return proxyConfig;
83 | }
84 |
85 | public PDownConfigInfo setProxyConfig(ProxyConfig proxyConfig) {
86 | this.proxyConfig = proxyConfig;
87 | return this;
88 | }
89 |
90 | public boolean isAutoOpen() {
91 | return autoOpen;
92 | }
93 |
94 | public PDownConfigInfo setAutoOpen(boolean autoOpen) {
95 | this.autoOpen = autoOpen;
96 | return this;
97 | }
98 |
99 | public static com.github.monkeywie.proxyee.proxy.ProxyConfig convert(ProxyConfig proxyConfig) {
100 | if (proxyConfig == null) {
101 | return null;
102 | }
103 | return new com.github.monkeywie.proxyee.proxy.ProxyConfig(
104 | com.github.monkeywie.proxyee.proxy.ProxyType.valueOf(proxyConfig.getProxyType().name()),
105 | proxyConfig.getHost(),
106 | proxyConfig.getPort(),
107 | proxyConfig.getUser(),
108 | proxyConfig.getPwd());
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/ContentScript.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import java.util.Arrays;
4 |
5 | public class ContentScript {
6 |
7 | private String[] matches;
8 | private String[] scripts;
9 |
10 | public String[] getMatches() {
11 | return matches;
12 | }
13 |
14 | public ContentScript setMatches(String[] matches) {
15 | this.matches = matches;
16 | return this;
17 | }
18 |
19 | public String[] getScripts() {
20 | return scripts;
21 | }
22 |
23 | public ContentScript setScripts(String[] scripts) {
24 | this.scripts = scripts;
25 | return this;
26 | }
27 |
28 | public boolean isMatch(String url) {
29 | if (matches != null
30 | && Arrays.stream(matches).anyMatch(m -> url.matches(m))) {
31 | return true;
32 | }
33 | return false;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/ExtensionConfig.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import java.util.List;
4 |
5 | public class ExtensionConfig {
6 |
7 | //本地加载的扩展
8 | private List localExtensions;
9 |
10 | public List getLocalExtensions() {
11 | return localExtensions;
12 | }
13 |
14 | public void setLocalExtensions(List localExtensions) {
15 | this.localExtensions = localExtensions;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/ExtensionContent.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import com.fasterxml.jackson.databind.DeserializationFeature;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.IOException;
8 | import java.util.ArrayList;
9 | import java.util.HashMap;
10 | import java.util.HashSet;
11 | import java.util.List;
12 | import java.util.Set;
13 | import org.pdown.core.util.FileUtil;
14 | import org.pdown.rest.util.ContentUtil;
15 | import org.pdown.rest.util.PathUtil;
16 |
17 | public class ExtensionContent {
18 |
19 | public static final String EXT_DIR = PathUtil.ROOT_PATH + File.separator + "extensions";
20 | public static final String EXT_DIR_CONFIG = EXT_DIR + File.separator + "ext.cfg";
21 | private static final String EXT_MANIFEST = "manifest.json";
22 |
23 | private static List EXTENSION_INFO_LIST;
24 | //代理服务器域名通配符列表
25 | private static Set PROXY_WILDCARDS;
26 | //需要嗅探下载的url正则表达式列表
27 | private static Set SNIFF_REGEXS;
28 | //配置
29 | private static ExtensionConfig CONFIG;
30 |
31 | public static void load() throws IOException {
32 | File file = new File(EXT_DIR);
33 | if (EXTENSION_INFO_LIST == null) {
34 | EXTENSION_INFO_LIST = new ArrayList<>();
35 | } else {
36 | EXTENSION_INFO_LIST.clear();
37 | }
38 | if (file.exists() && file.isDirectory()) {
39 | //加载所有已安装的扩展
40 | for (File extendDir : file.listFiles()) {
41 | if (extendDir.isDirectory()) {
42 | //读取manifest.json
43 | ExtensionInfo extensionInfo = parseExtensionDir(extendDir);
44 | if (extensionInfo != null) {
45 | EXTENSION_INFO_LIST.add(extensionInfo);
46 | }
47 | }
48 | }
49 | }
50 | //加载本地安装的扩展
51 | try {
52 | CONFIG = ContentUtil.get(EXT_DIR_CONFIG, ExtensionConfig.class);
53 | } catch (Exception e) {
54 | }
55 | if (CONFIG == null) {
56 | CONFIG = new ExtensionConfig();
57 | } else if (CONFIG.getLocalExtensions() != null) {
58 | for (String localExtendDir : CONFIG.getLocalExtensions()) {
59 | File extendDir = new File(localExtendDir);
60 | if (extendDir.isDirectory()) {
61 | //读取manifest.json
62 | ExtensionInfo extensionInfo = parseExtensionDir(extendDir, true);
63 | if (extensionInfo != null) {
64 | EXTENSION_INFO_LIST.add(extensionInfo);
65 | }
66 | }
67 | }
68 | }
69 | refresh();
70 | }
71 |
72 | public static ExtensionConfig getConfig() {
73 | return CONFIG;
74 | }
75 |
76 | public synchronized static void saveConfig() throws IOException {
77 | ContentUtil.save(CONFIG, EXT_DIR_CONFIG);
78 | }
79 |
80 | public synchronized static ExtensionInfo refresh(String path, boolean isLocal) throws IOException {
81 | ExtensionInfo loadExt = parseExtensionDir(new File((isLocal ? "" : EXT_DIR) + path), isLocal);
82 | if (loadExt != null && EXTENSION_INFO_LIST != null && path != null) {
83 | boolean match = false;
84 | for (int i = 0; i < EXTENSION_INFO_LIST.size(); i++) {
85 | ExtensionInfo extensionInfo = EXTENSION_INFO_LIST.get(i);
86 | if (loadExt.getMeta().getPath().equals(extensionInfo.getMeta().getPath())
87 | && loadExt.getMeta().isLocal() == extensionInfo.getMeta().isLocal()) {
88 | match = true;
89 | EXTENSION_INFO_LIST.set(i, loadExt);
90 | break;
91 | }
92 | }
93 | if (!match) {
94 | EXTENSION_INFO_LIST.add(loadExt);
95 | if (isLocal) {
96 | //保存文件
97 | if (CONFIG.getLocalExtensions() == null) {
98 | CONFIG.setLocalExtensions(new ArrayList<>());
99 | }
100 | CONFIG.getLocalExtensions().add(path);
101 | ExtensionContent.saveConfig();
102 | }
103 | }
104 | refresh();
105 | }
106 | return loadExt;
107 | }
108 |
109 | public synchronized static ExtensionInfo refresh(String path) throws IOException {
110 | return refresh(path, false);
111 | }
112 |
113 | public synchronized static void remove(String path, boolean isLocal) throws IOException {
114 | if (EXTENSION_INFO_LIST != null && path != null) {
115 | for (int i = 0; i < EXTENSION_INFO_LIST.size(); i++) {
116 | ExtensionInfo extensionInfo = EXTENSION_INFO_LIST.get(i);
117 | if (path.equals(extensionInfo.getMeta().getPath())
118 | && extensionInfo.getMeta().isLocal() == isLocal) {
119 | EXTENSION_INFO_LIST.remove(i);
120 | if (!extensionInfo.getMeta().isLocal()) {
121 | FileUtil.deleteIfExists(extensionInfo.getMeta().getFullPath());
122 | } else {
123 | //保存文件
124 | if (CONFIG.getLocalExtensions() != null) {
125 | CONFIG.getLocalExtensions().remove(extensionInfo.getMeta().getFullPath());
126 | ExtensionContent.saveConfig();
127 | }
128 | }
129 | break;
130 | }
131 | }
132 | refresh();
133 | }
134 | }
135 |
136 | public synchronized static void refresh() {
137 | if (PROXY_WILDCARDS == null) {
138 | PROXY_WILDCARDS = new HashSet<>();
139 | } else {
140 | PROXY_WILDCARDS.clear();
141 | }
142 | if (SNIFF_REGEXS == null) {
143 | SNIFF_REGEXS = new HashSet<>();
144 | } else {
145 | SNIFF_REGEXS.clear();
146 | }
147 | if (EXTENSION_INFO_LIST != null) {
148 | for (ExtensionInfo extensionInfo : EXTENSION_INFO_LIST) {
149 | if (extensionInfo.getMeta().isEnabled()) {
150 | //读取需要代理的域名匹配符
151 | if (extensionInfo.getProxyWildcards() != null) {
152 | for (String wildcard : extensionInfo.getProxyWildcards()) {
153 | PROXY_WILDCARDS.add(wildcard.trim());
154 | }
155 | }
156 | //读取需要嗅探下载的url正则表达式
157 | if (extensionInfo.getSniffRegexs() != null) {
158 | for (String regex : extensionInfo.getSniffRegexs()) {
159 | SNIFF_REGEXS.add(regex.trim());
160 | }
161 | }
162 | }
163 | }
164 | }
165 | }
166 |
167 | private static ExtensionInfo parseExtensionDir(File extendDir, boolean isLocal) {
168 | ExtensionInfo extensionInfo = null;
169 | ObjectMapper objectMapper = new ObjectMapper();
170 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
171 | try {
172 | extensionInfo = objectMapper.readValue(new FileInputStream(extendDir + File.separator + EXT_MANIFEST), ExtensionInfo.class);
173 | } catch (IOException e) {
174 | }
175 | if (extensionInfo != null) {
176 | Meta meta = Meta.load(extendDir.getPath());
177 | meta.setLocal(isLocal);
178 | //如果没有设置则生成默认设置信息
179 | if (extensionInfo.getSettings() != null
180 | && extensionInfo.getSettings().size() > 0) {
181 | if (meta.getSettings() == null) {
182 | meta.setSettings(new HashMap<>());
183 | }
184 | if (meta.getSettings().size() == 0) {
185 | for (Setting setting : extensionInfo.getSettings()) {
186 | meta.getSettings().put(setting.getName(), setting.getValue());
187 | }
188 | }
189 | }
190 | extensionInfo.setMeta(meta);
191 | }
192 | return extensionInfo;
193 | }
194 |
195 | private static ExtensionInfo parseExtensionDir(File extendDir) {
196 | return parseExtensionDir(extendDir, false);
197 | }
198 |
199 | public static List get() {
200 | return EXTENSION_INFO_LIST;
201 | }
202 |
203 | public static Set getProxyWildCards() {
204 | return PROXY_WILDCARDS;
205 | }
206 |
207 | public static Set getSniffRegexs() {
208 | return SNIFF_REGEXS;
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/ExtensionInfo.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import java.util.List;
4 |
5 | public class ExtensionInfo {
6 |
7 | private String title; //扩展名称
8 | private double version; //扩展版本号
9 | private String homepage; //扩展主页
10 | private String description; //扩展描述
11 | private List proxyWildcards; //扩展生效配置的域名通配符列表
12 | private List sniffRegexs; //扩展嗅探下载的url正则表达式列表
13 | private List contentScripts;
14 | private HookScript hookScript; //下载状态变更时触发的钩子函数脚本
15 | private List settings; //扩展设置选项
16 | private Meta meta;
17 |
18 | public String getTitle() {
19 | return title;
20 | }
21 |
22 | public ExtensionInfo setTitle(String title) {
23 | this.title = title;
24 | return this;
25 | }
26 |
27 | public double getVersion() {
28 | return version;
29 | }
30 |
31 | public ExtensionInfo setVersion(double version) {
32 | this.version = version;
33 | return this;
34 | }
35 |
36 | public String getHomepage() {
37 | return homepage;
38 | }
39 |
40 | public ExtensionInfo setHomepage(String homepage) {
41 | this.homepage = homepage;
42 | return this;
43 | }
44 |
45 | public String getDescription() {
46 | return description;
47 | }
48 |
49 | public ExtensionInfo setDescription(String description) {
50 | this.description = description;
51 | return this;
52 | }
53 |
54 | public List getProxyWildcards() {
55 | return proxyWildcards;
56 | }
57 |
58 | public ExtensionInfo setProxyWildcards(List proxyWildcards) {
59 | this.proxyWildcards = proxyWildcards;
60 | return this;
61 | }
62 |
63 | public List getSniffRegexs() {
64 | return sniffRegexs;
65 | }
66 |
67 | public ExtensionInfo setSniffRegexs(List sniffRegexs) {
68 | this.sniffRegexs = sniffRegexs;
69 | return this;
70 | }
71 |
72 | public List getContentScripts() {
73 | return contentScripts;
74 | }
75 |
76 | public ExtensionInfo setContentScripts(List contentScripts) {
77 | this.contentScripts = contentScripts;
78 | return this;
79 | }
80 |
81 | public HookScript getHookScript() {
82 | return hookScript;
83 | }
84 |
85 | public ExtensionInfo setHookScript(HookScript hookScript) {
86 | this.hookScript = hookScript;
87 | return this;
88 | }
89 |
90 | public List getSettings() {
91 | return settings;
92 | }
93 |
94 | public ExtensionInfo setSettings(List settings) {
95 | this.settings = settings;
96 | return this;
97 | }
98 |
99 | public Meta getMeta() {
100 | return meta;
101 | }
102 |
103 | public ExtensionInfo setMeta(Meta meta) {
104 | this.meta = meta;
105 | return this;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/HookScript.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import java.util.Arrays;
4 |
5 | public class HookScript {
6 |
7 | public static final String EVENT_RESOLVE = "resolve";
8 | public static final String EVENT_START = "start";
9 | public static final String EVENT_RESUME = "resume";
10 | public static final String EVENT_PAUSE = "pause";
11 | public static final String EVENT_ERROR = "error";
12 | public static final String EVENT_DONE = "done";
13 | public static final String EVENT_DELETE = "delete";
14 |
15 | private Event[] events;
16 | private String script;
17 |
18 | public Event[] getEvents() {
19 | return events;
20 | }
21 |
22 | public HookScript setEvents(Event[] events) {
23 | this.events = events;
24 | return this;
25 | }
26 |
27 | public String getScript() {
28 | return script;
29 | }
30 |
31 | public HookScript setScript(String script) {
32 | this.script = script;
33 | return this;
34 | }
35 |
36 | /**
37 | * 判断扩展是否有注册钩子函数
38 | */
39 | public Event hasEvent(String event, String url) {
40 | String matchUrl = url != null ? url.replaceAll("^(?i)(https?://)", "") : "";
41 | if (events != null) {
42 | return Arrays.stream(events)
43 | .filter(e -> event.equalsIgnoreCase(e.getOn()) && (e.getMatches() == null || (Arrays.stream(e.getMatches()).anyMatch(m -> matchUrl.matches(m)))))
44 | .findFirst()
45 | .orElse(null);
46 | }
47 | return null;
48 | }
49 |
50 | public static class Event {
51 |
52 | private String on;
53 | private String[] matches;
54 | private String method;
55 |
56 | public String getOn() {
57 | return on;
58 | }
59 |
60 | public Event setOn(String on) {
61 | this.on = on;
62 | return this;
63 | }
64 |
65 | public String[] getMatches() {
66 | return matches;
67 | }
68 |
69 | public Event setMatches(String[] matches) {
70 | this.matches = matches;
71 | return this;
72 | }
73 |
74 | public String getMethod() {
75 | return method;
76 | }
77 |
78 | public Event setMethod(String method) {
79 | this.method = method;
80 | return this;
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/Meta.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.Map;
6 | import org.pdown.rest.util.ContentUtil;
7 |
8 | public class Meta {
9 |
10 | public transient static final String CONFIG_FILE = ".ext_data/.config.dat";
11 |
12 | private transient String path;
13 | private transient String fullPath;
14 | private boolean enabled = true;
15 | private boolean local = true;
16 | private Map settings;
17 | private Map data;
18 |
19 | public String getPath() {
20 | return path;
21 | }
22 |
23 | public Meta setPath(String path) {
24 | this.path = path;
25 | return this;
26 | }
27 |
28 | public String getFullPath() {
29 | return fullPath;
30 | }
31 |
32 | public Meta setFullPath(String fullPath) {
33 | this.fullPath = fullPath;
34 | return this;
35 | }
36 |
37 | public boolean isEnabled() {
38 | return enabled;
39 | }
40 |
41 | public Meta setEnabled(boolean enabled) {
42 | this.enabled = enabled;
43 | return this;
44 | }
45 |
46 | public Map getSettings() {
47 | return settings;
48 | }
49 |
50 | public Meta setSettings(Map settings) {
51 | this.settings = settings;
52 | return this;
53 | }
54 |
55 | public Map getData() {
56 | return data;
57 | }
58 |
59 | public Meta setData(Map data) {
60 | this.data = data;
61 | return this;
62 | }
63 |
64 | public boolean isLocal() {
65 | return local;
66 | }
67 |
68 | public void setLocal(boolean local) {
69 | this.local = local;
70 | }
71 |
72 | public void save() {
73 | try {
74 | ContentUtil.save(this, getFullPath() + File.separator + CONFIG_FILE, true);
75 | } catch (IOException e) {
76 | }
77 | }
78 |
79 | public static Meta load(String path) {
80 | Meta meta = null;
81 | try {
82 | meta = ContentUtil.get(path + File.separator + CONFIG_FILE, Meta.class);
83 | } catch (IOException e) {
84 | }
85 | if (meta == null) {
86 | meta = new Meta();
87 | }
88 | meta.setPath("/" + new File(path).getName());
89 | meta.setFullPath(path);
90 | return meta;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/Setting.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension;
2 |
3 | import java.util.Map;
4 |
5 | public class Setting {
6 |
7 | private String name;
8 | private String title;
9 | private String type;
10 | private Object value;
11 | private String description;
12 | private boolean isMultiple;
13 | private Map options;
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public Setting setName(String name) {
20 | this.name = name;
21 | return this;
22 | }
23 |
24 | public String getTitle() {
25 | return title;
26 | }
27 |
28 | public Setting setTitle(String title) {
29 | this.title = title;
30 | return this;
31 | }
32 |
33 | public String getType() {
34 | return type;
35 | }
36 |
37 | public Setting setType(String type) {
38 | this.type = type;
39 | return this;
40 | }
41 |
42 | public Object getValue() {
43 | return value;
44 | }
45 |
46 | public Setting setValue(Object value) {
47 | this.value = value;
48 | return this;
49 | }
50 |
51 | public String getDescription() {
52 | return description;
53 | }
54 |
55 | public Setting setDescription(String description) {
56 | this.description = description;
57 | return this;
58 | }
59 |
60 | public boolean isMultiple() {
61 | return isMultiple;
62 | }
63 |
64 | public Setting setMultiple(boolean multiple) {
65 | isMultiple = multiple;
66 | return this;
67 | }
68 |
69 | public Map getOptions() {
70 | return options;
71 | }
72 |
73 | public Setting setOptions(Map options) {
74 | this.options = options;
75 | return this;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/jsruntime/JavascriptEngine.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.jsruntime;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import javax.script.Invocable;
5 | import javax.script.ScriptContext;
6 | import javax.script.ScriptEngine;
7 | import javax.script.ScriptEngineManager;
8 | import javax.script.ScriptException;
9 | import javax.script.SimpleScriptContext;
10 | import jdk.nashorn.api.scripting.ClassFilter;
11 | import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
12 | import org.pdown.gui.extension.jsruntime.polyfill.Window;
13 | import org.pdown.rest.form.HttpRequestForm;
14 |
15 | public class JavascriptEngine {
16 |
17 | public static ScriptEngine buildEngine() throws ScriptException, NoSuchMethodException {
18 | NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
19 | ScriptEngine engine = factory.getScriptEngine(new SafeClassFilter());
20 | Window window = new Window();
21 | Object global = engine.eval("this");
22 | Object jsObject = engine.eval("Object");
23 | Invocable invocable = (Invocable) engine;
24 | invocable.invokeMethod(jsObject, "bindProperties", global, window);
25 | engine.eval("var window = this");
26 | return engine;
27 | }
28 |
29 | /**
30 | * 禁止任何显式调用java代码
31 | */
32 | private static class SafeClassFilter implements ClassFilter {
33 |
34 | @Override
35 | public boolean exposeToScripts(String s) {
36 | return false;
37 | }
38 | }
39 |
40 | public static void main(String[] args) throws ScriptException, NoSuchMethodException, JsonProcessingException, InterruptedException {
41 | ScriptEngine engine = buildEngine();
42 | Invocable invocable = (Invocable) engine;
43 | engine.eval("load('E:/study/extensions/bilibili-helper/dist/hook.js')");
44 | HttpRequestForm requestForm = new HttpRequestForm();
45 | requestForm.setUrl("https://www.bilibili.com/video/av34765642");
46 | Object result = invocable.invokeFunction("error");
47 | ScriptContext ctx = new SimpleScriptContext();
48 | ctx.setAttribute("result", result, ScriptContext.ENGINE_SCOPE);
49 | System.out.println(engine.eval("!!result&&typeof result=='object'&&typeof result.then=='function'", ctx));
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/Window.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.jsruntime.polyfill;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import java.util.concurrent.atomic.AtomicLong;
5 | import java.util.function.Function;
6 | import jdk.internal.dynalink.beans.StaticClass;
7 | import org.pdown.gui.extension.jsruntime.polyfill.property.Console;
8 | import org.pdown.gui.extension.jsruntime.polyfill.property.Document;
9 |
10 | public class Window {
11 |
12 | private static final String TIMEOUT_THREAD_NAME = "babel4j-timeout-";
13 | private static final String INTERVAL_THREAD_NAME = "babel4j-interval-";
14 | private static final AtomicLong THREAD_ID = new AtomicLong(0);
15 |
16 | public Console console = new Console();
17 | public Document document = new Document();
18 | public StaticClass XMLHttpRequest = StaticClass.forClass(org.pdown.gui.extension.jsruntime.polyfill.property.XMLHttpRequest.class);
19 |
20 | public long setTimeout(Function function, long timeout) {
21 | Long id = THREAD_ID.addAndGet(1);
22 | new Thread(() -> {
23 | try {
24 | TimeUnit.MILLISECONDS.sleep(timeout);
25 | function.apply(null);
26 | } catch (InterruptedException e) {
27 | }
28 | }, TIMEOUT_THREAD_NAME + id).start();
29 | return id;
30 | }
31 |
32 | public void clearTimeout(Long id) {
33 | Thread temp = Thread.getAllStackTraces().keySet().stream()
34 | .filter(thread -> (TIMEOUT_THREAD_NAME + id).equals(thread.getName()))
35 | .findFirst()
36 | .orElse(null);
37 | if (temp != null) {
38 | temp.interrupt();
39 | }
40 | }
41 |
42 | public long setInterval(Function function, long timeout) {
43 | Long id = THREAD_ID.addAndGet(1);
44 | new Thread(() -> {
45 | try {
46 | while (true) {
47 | TimeUnit.MILLISECONDS.sleep(timeout);
48 | function.apply(null);
49 | }
50 | } catch (InterruptedException e) {
51 | }
52 | }, INTERVAL_THREAD_NAME + id).start();
53 | return id;
54 | }
55 |
56 | public void clearInterval(Long id) {
57 | Thread temp = Thread.getAllStackTraces().keySet().stream()
58 | .filter(thread -> (INTERVAL_THREAD_NAME + id).equals(thread.getName()))
59 | .findFirst()
60 | .orElse(null);
61 | if (temp != null) {
62 | temp.interrupt();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Console.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.jsruntime.polyfill.property;
2 |
3 | public class Console {
4 |
5 | public void log(Object object) {
6 | System.out.println(object);
7 | }
8 |
9 | public void debug(Object object) {
10 | System.out.println(object);
11 | }
12 |
13 | public void error(Object object) {
14 | if (object instanceof Throwable) {
15 | Throwable throwable = (Throwable) object;
16 | throwable.printStackTrace();
17 | } else {
18 | System.out.println(object);
19 | }
20 | }
21 |
22 | public void error(Object msg, Object throwable) {
23 | if (throwable instanceof Throwable) {
24 | ((Throwable) throwable).printStackTrace();
25 | } else {
26 | System.out.println(msg);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Document.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.jsruntime.polyfill.property;
2 |
3 | public class Document {
4 |
5 | private String cookie;
6 |
7 | public String getCookie() {
8 | return cookie;
9 | }
10 |
11 | public void setCookie(String cookie) {
12 | this.cookie = cookie;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/XMLHttpRequest.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.jsruntime.polyfill.property;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.io.OutputStream;
8 | import java.net.HttpURLConnection;
9 | import java.net.InetSocketAddress;
10 | import java.net.Proxy;
11 | import java.net.Proxy.Type;
12 | import java.net.URL;
13 | import java.nio.charset.Charset;
14 | import java.util.LinkedHashMap;
15 | import java.util.Map;
16 | import java.util.function.Function;
17 | import java.util.regex.Matcher;
18 | import java.util.regex.Pattern;
19 | import java.util.stream.Collectors;
20 | import java.util.zip.GZIPInputStream;
21 | import org.springframework.util.StringUtils;
22 |
23 | public class XMLHttpRequest {
24 |
25 | private String method;
26 | private String url;
27 |
28 | public Function onreadystatechange;
29 | public int readyState = 0;
30 | public int status = 0;
31 | public String responseText;
32 |
33 | private Map customRequestHeads = new LinkedHashMap<>();
34 | private Map responseHeads = new LinkedHashMap<>();
35 |
36 | public void setRequestHeader(String header, String value) {
37 | customRequestHeads.put(header, value);
38 | }
39 |
40 | public String getResponseHeader(String header) {
41 | return responseHeads.get(header.toLowerCase());
42 | }
43 |
44 | public void open(String method, String url) {
45 | this.method = method;
46 | this.url = url;
47 | }
48 |
49 | public void open(String method, String url, boolean async) {
50 | this.open(method, url);
51 | }
52 |
53 | private static String DEFAULT_UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36";
54 |
55 | public void send(String data) throws IOException {
56 | URL u = new URL(url);
57 | HttpURLConnection connection = (HttpURLConnection) u.openConnection();
58 | readystatechange(1);
59 | connection.setRequestMethod(method.toUpperCase());
60 | connection.setRequestProperty("User-Agent", DEFAULT_UA);
61 | customRequestHeads.entrySet().stream().forEach(entry -> connection.setRequestProperty(entry.getKey(), entry.getValue()));
62 | connection.setDoOutput(true);
63 | if (data != null && data.trim().length() > 0) {
64 | try (
65 | OutputStream outputStream = connection.getOutputStream()
66 | ) {
67 | outputStream.write(data.getBytes(Charset.forName("UTF-8")));
68 | }
69 | }
70 | int code = connection.getResponseCode();
71 | connection.getHeaderFields().entrySet().forEach(entry -> {
72 | if (entry.getKey() != null) {
73 | responseHeads.put(entry.getKey().toLowerCase(), entry.getValue().stream().collect(Collectors.joining("; ")));
74 | }
75 | }
76 | );
77 | readystatechange(2, code);
78 | String charset = "UTF-8";
79 | String contentType = connection.getContentType();
80 | if (!StringUtils.isEmpty(contentType)) {
81 | Pattern pattern = Pattern.compile("charset=(.*)$", Pattern.CASE_INSENSITIVE);
82 | Matcher matcher = pattern.matcher(contentType);
83 | if (matcher.find()) {
84 | charset = matcher.group(1);
85 | }
86 | }
87 | InputStream inputStream = code != 200 ? connection.getErrorStream() : connection.getInputStream();
88 | if (responseHeads.entrySet().stream().anyMatch(entry -> "Content-Encoding".equalsIgnoreCase(entry.getKey()) && entry.getValue().matches("^.*(?i)(gzip).*$"))) {
89 | inputStream = new GZIPInputStream(inputStream);
90 | }
91 | try (
92 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset))
93 | ) {
94 | readystatechange(3, code);
95 | responseText = reader.lines().collect(Collectors.joining("\n"));
96 | readystatechange(4, code);
97 | }
98 | }
99 |
100 | public static void main(String[] args) throws IOException {
101 | URL u = new URL("http://www.baidu.com");
102 | Proxy proxy = new Proxy(Type.SOCKS, new InetSocketAddress("127.0.0.1", 1088));
103 | HttpURLConnection connection = (HttpURLConnection) u.openConnection(proxy);
104 | System.out.println(connection.getResponseCode());
105 | }
106 |
107 | public void send() throws IOException {
108 | send(null);
109 | }
110 |
111 | private void readystatechange(int readyState, int status) {
112 | this.readyState = readyState;
113 | this.status = status;
114 | if (onreadystatechange != null) {
115 | onreadystatechange.apply(null);
116 | }
117 | }
118 |
119 | private void readystatechange(int readyState) {
120 | readystatechange(readyState, status);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/intercept/AjaxIntercept.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.intercept;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;
5 | import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
6 | import io.netty.channel.Channel;
7 | import io.netty.handler.codec.http.*;
8 | import io.netty.util.AsciiString;
9 | import org.springframework.util.StringUtils;
10 |
11 | import java.io.ByteArrayOutputStream;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.io.OutputStream;
15 | import java.net.HttpURLConnection;
16 | import java.net.URL;
17 | import java.net.URLDecoder;
18 | import java.util.Map;
19 |
20 | /**
21 | * 通过代理服务器代理ajax请求,避免浏览器CORS问题
22 | */
23 | public class AjaxIntercept extends HttpProxyIntercept {
24 |
25 | private static final String PROXY_SEND_KEY = "X-Proxy-Send";
26 |
27 | private boolean proxyFlag;
28 |
29 | @Override
30 | public void beforeRequest(Channel clientChannel, HttpRequest httpRequest, HttpProxyInterceptPipeline pipeline) throws Exception {
31 | proxyFlag = httpRequest.headers().contains(PROXY_SEND_KEY);
32 | super.beforeRequest(clientChannel, httpRequest, pipeline);
33 | }
34 |
35 | @Override
36 | public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) throws Exception {
37 | if (proxyFlag) {
38 | httpResponse.setStatus(HttpResponseStatus.OK);
39 | proxyChannel.close();
40 | ObjectMapper objectMapper = new ObjectMapper();
41 | LastHttpContent content = new DefaultLastHttpContent();
42 | String proxyRequestRaw = URLDecoder.decode(pipeline.getHttpRequest().headers().get(PROXY_SEND_KEY), "utf-8");
43 | try {
44 | ProxyRequest proxyRequest = objectMapper.readValue(proxyRequestRaw, ProxyRequest.class);
45 | ProxyResponse proxyResponse = doRequest(proxyRequest);
46 | httpResponse.setStatus(HttpResponseStatus.valueOf(proxyResponse.getStatus()));
47 | content.content().writeBytes(proxyResponse.getData());
48 | } catch (IOException e) {
49 | e.printStackTrace();
50 | httpResponse.setStatus(HttpResponseStatus.SERVICE_UNAVAILABLE);
51 | }
52 | httpResponse.headers().remove(HttpHeaderNames.CONTENT_ENCODING);
53 | httpResponse.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
54 | httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.content().readableBytes());
55 | httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, AsciiString.cached("application/json; charset=utf-8"));
56 | super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
57 | clientChannel.writeAndFlush(content);
58 | } else {
59 | super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
60 | }
61 | }
62 |
63 | @Override
64 | public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpContent httpContent, HttpProxyInterceptPipeline pipeline) throws Exception {
65 | if (!proxyFlag) {
66 | super.afterResponse(clientChannel, proxyChannel, httpContent, pipeline);
67 | }
68 | }
69 |
70 | private ProxyResponse doRequest(ProxyRequest proxyRequest) throws IOException {
71 | URL url = new URL(proxyRequest.getUrl());
72 | HttpURLConnection connection = (HttpURLConnection) url.openConnection();
73 | connection.setRequestMethod(proxyRequest.getMethod().toUpperCase());
74 | connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
75 | if (proxyRequest.getHeads() != null) {
76 | for (Map.Entry entry : proxyRequest.getHeads().entrySet()) {
77 | connection.setRequestProperty(entry.getKey(), entry.getValue());
78 | }
79 | }
80 | connection.setDoInput(true);
81 | if (!StringUtils.isEmpty(proxyRequest.getRawData())) {
82 | connection.setDoOutput(true);
83 | try (
84 | OutputStream output = connection.getOutputStream()
85 | ) {
86 | output.write(proxyRequest.getRawData().getBytes());
87 | output.flush();
88 | }
89 | } else if (proxyRequest.getData() != null) {
90 | connection.setDoOutput(true);
91 | try (
92 | OutputStream output = connection.getOutputStream()
93 | ) {
94 | ObjectMapper objectMapper = new ObjectMapper();
95 | output.write(objectMapper.writeValueAsBytes(proxyRequest.getData()));
96 | output.flush();
97 | }
98 | }
99 | ProxyResponse proxyResponse = new ProxyResponse();
100 | proxyResponse.setStatus(connection.getResponseCode());
101 | try (
102 | ByteArrayOutputStream output = new ByteArrayOutputStream();
103 | InputStream input = connection.getResponseCode() == 200 ? connection.getInputStream() : connection.getErrorStream()
104 | ) {
105 | byte[] bts = new byte[8192];
106 | int len;
107 | while ((len = input.read(bts)) != -1) {
108 | output.write(bts, 0, len);
109 | }
110 | proxyResponse.setData(output.toByteArray());
111 | return proxyResponse;
112 | }
113 | }
114 |
115 | static class ProxyRequest {
116 |
117 | private String method;
118 | private String url;
119 | private Map heads;
120 | private Map data;
121 | private String rawData;
122 |
123 |
124 | public String getMethod() {
125 | return method;
126 | }
127 |
128 | public void setMethod(String method) {
129 | this.method = method;
130 | }
131 |
132 | public String getUrl() {
133 | return url;
134 | }
135 |
136 | public void setUrl(String url) {
137 | this.url = url;
138 | }
139 |
140 | public Map getHeads() {
141 | return heads;
142 | }
143 |
144 | public void setHeads(Map heads) {
145 | this.heads = heads;
146 | }
147 |
148 | public Map getData() {
149 | return data;
150 | }
151 |
152 | public void setData(Map data) {
153 | this.data = data;
154 | }
155 |
156 | public String getRawData() {
157 | return rawData;
158 | }
159 |
160 | public void setRawData(String rawData) {
161 | this.rawData = rawData;
162 | }
163 | }
164 |
165 | static class ProxyResponse {
166 |
167 | private int status;
168 | private byte[] data;
169 |
170 | public int getStatus() {
171 | return status;
172 | }
173 |
174 | public void setStatus(int status) {
175 | this.status = status;
176 | }
177 |
178 | public byte[] getData() {
179 | return data;
180 | }
181 |
182 | public void setData(byte[] data) {
183 | this.data = data;
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/intercept/CookieIntercept.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.intercept;
2 |
3 | import com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;
4 | import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
5 | import io.netty.channel.Channel;
6 | import io.netty.handler.codec.http.DefaultHttpHeaders;
7 | import io.netty.handler.codec.http.DefaultHttpResponse;
8 | import io.netty.handler.codec.http.DefaultLastHttpContent;
9 | import io.netty.handler.codec.http.HttpHeaderNames;
10 | import io.netty.handler.codec.http.HttpRequest;
11 | import io.netty.handler.codec.http.HttpResponse;
12 | import io.netty.handler.codec.http.HttpResponseStatus;
13 | import io.netty.handler.codec.http.HttpVersion;
14 | import io.netty.util.AsciiString;
15 | import io.netty.util.internal.StringUtil;
16 | import java.net.URL;
17 |
18 | /**
19 | * 嗅探目标网站cookie,支持HTTP only
20 | */
21 | public class CookieIntercept extends HttpProxyIntercept {
22 |
23 | @Override
24 | public void beforeRequest(Channel clientChannel, HttpRequest httpRequest, HttpProxyInterceptPipeline pipeline) throws Exception {
25 | String acceptValue = httpRequest.headers().get(HttpHeaderNames.ACCEPT);
26 | if (acceptValue != null && acceptValue.contains("application/x-sniff-cookie")) {
27 | HttpResponse httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, new DefaultHttpHeaders());
28 | httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
29 | //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
30 | AsciiString customHeadKey = AsciiString.cached("X-Sniff-Cookie");
31 | String cookie = pipeline.getHttpRequest().headers().get(HttpHeaderNames.COOKIE);
32 | httpResponse.headers().set(customHeadKey, cookie == null ? "" : cookie);
33 | httpResponse.headers().set(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS, customHeadKey);
34 | String origin = httpRequest.headers().get(HttpHeaderNames.ORIGIN);
35 | if (StringUtil.isNullOrEmpty(origin)) {
36 | String referer = httpRequest.headers().get(HttpHeaderNames.REFERER);
37 | URL url = new URL(referer);
38 | origin = url.getHost();
39 | }
40 | httpResponse.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
41 | httpResponse.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, true);
42 | clientChannel.writeAndFlush(httpResponse);
43 | clientChannel.writeAndFlush(new DefaultLastHttpContent());
44 | clientChannel.close();
45 | } else {
46 | super.beforeRequest(clientChannel, httpRequest, pipeline);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/intercept/ScriptIntercept.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.intercept;
2 |
3 | import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
4 | import com.github.monkeywie.proxyee.intercept.common.FullResponseIntercept;
5 | import com.github.monkeywie.proxyee.util.ByteUtil;
6 | import io.netty.handler.codec.http.FullHttpResponse;
7 | import io.netty.handler.codec.http.HttpRequest;
8 | import io.netty.handler.codec.http.HttpResponse;
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.nio.charset.Charset;
12 | import java.nio.file.Files;
13 | import java.util.List;
14 | import org.pdown.core.util.HttpDownUtil;
15 | import org.pdown.gui.extension.ContentScript;
16 | import org.pdown.gui.extension.ExtensionContent;
17 | import org.pdown.gui.extension.ExtensionInfo;
18 | import org.pdown.gui.extension.util.ExtensionUtil;
19 |
20 | public class ScriptIntercept extends FullResponseIntercept {
21 |
22 | @Override
23 | public boolean match(HttpRequest httpRequest, HttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) {
24 | return isHtml(httpRequest, httpResponse);
25 | }
26 |
27 | private static final String INSERT_TOKEN = "";
28 | private static final String INIT_TEMPLATE = ";(function (pdown) {\n"
29 | + " ${content}\n"
30 | + "})(${runtime})";
31 |
32 | private String readInsertTemplate(ExtensionInfo extensionInfo) {
33 | String js = ExtensionUtil.readRuntimeTemplate(extensionInfo);
34 | js = INIT_TEMPLATE.replace("${runtime}", js);
35 | js = "";
36 | return js;
37 | }
38 |
39 | @Override
40 | public void handelResponse(HttpRequest httpRequest, FullHttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) {
41 | List extensionInfoList = ExtensionContent.get();
42 | if (isEmpty(extensionInfoList)) {
43 | return;
44 | }
45 | for (ExtensionInfo extensionInfo : extensionInfoList) {
46 | if (isEmpty(extensionInfo.getContentScripts())) {
47 | continue;
48 | }
49 | for (ContentScript contentScript : extensionInfo.getContentScripts()) {
50 | //扩展注入正则表达式与当前访问的url匹配则注入脚本
51 | String url = HttpDownUtil.getUrl(httpRequest);
52 | if (contentScript.isMatch(url)) {
53 | String apiTemplate = readInsertTemplate(extensionInfo);
54 | StringBuilder scriptsBuilder = new StringBuilder();
55 | for (String script : contentScript.getScripts()) {
56 | File scriptFile = new File(extensionInfo.getMeta().getFullPath() + File.separator + script);
57 | if (scriptFile.exists() && scriptFile.isFile()) {
58 | try {
59 | scriptsBuilder.append(new String(Files.readAllBytes(scriptFile.toPath()), "UTF-8"));
60 | } catch (IOException e) {
61 | }
62 | }
63 | }
64 | apiTemplate = apiTemplate.replace("${content}", scriptsBuilder.toString());
65 | int index = ByteUtil.findText(httpResponse.content(), INSERT_TOKEN);
66 | ByteUtil.insertText(httpResponse.content(), index == -1 ? 0 : index - INSERT_TOKEN.length(), apiTemplate, Charset.forName("UTF-8"));
67 | }
68 | }
69 | }
70 | }
71 |
72 | private boolean isEmpty(List list) {
73 | return list == null || list.size() == 0;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/intercept/SniffIntercept.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.intercept;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;
5 | import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
6 | import com.github.monkeywie.proxyee.util.HttpUtil;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.PooledByteBufAllocator;
9 | import io.netty.channel.Channel;
10 | import io.netty.channel.nio.NioEventLoopGroup;
11 | import io.netty.handler.codec.http.DefaultLastHttpContent;
12 | import io.netty.handler.codec.http.HttpContent;
13 | import io.netty.handler.codec.http.HttpHeaderNames;
14 | import io.netty.handler.codec.http.HttpHeaderValues;
15 | import io.netty.handler.codec.http.HttpHeaders;
16 | import io.netty.handler.codec.http.HttpRequest;
17 | import io.netty.handler.codec.http.HttpResponse;
18 | import io.netty.handler.codec.http.LastHttpContent;
19 | import io.netty.util.ReferenceCountUtil;
20 | import java.net.URLEncoder;
21 | import java.util.Arrays;
22 | import java.util.Set;
23 | import org.pdown.core.entity.HttpRequestInfo;
24 | import org.pdown.core.entity.HttpResponseInfo;
25 | import org.pdown.core.util.HttpDownUtil;
26 | import org.pdown.core.util.ProtoUtil.RequestProto;
27 | import org.pdown.gui.DownApplication;
28 | import org.pdown.gui.extension.ExtensionContent;
29 | import org.pdown.rest.form.HttpRequestForm;
30 | import org.slf4j.Logger;
31 | import org.slf4j.LoggerFactory;
32 |
33 | public class SniffIntercept extends HttpProxyIntercept {
34 |
35 | private final static Logger LOGGER = LoggerFactory.getLogger(SniffIntercept.class);
36 |
37 | private boolean matchFlag = false;
38 |
39 | private ByteBuf content;
40 | private boolean downFlag = false;
41 |
42 | @Override
43 | public void beforeRequest(Channel clientChannel, HttpRequest httpRequest,
44 | HttpProxyInterceptPipeline pipeline) throws Exception {
45 | Set sniffRegexs = ExtensionContent.getSniffRegexs();
46 | if (sniffRegexs == null) {
47 | matchFlag = false;
48 | } else {
49 | matchFlag = sniffRegexs.stream().anyMatch(regex -> HttpUtil.checkUrl(httpRequest, regex));
50 | }
51 | if (!matchFlag) {
52 | super.beforeRequest(clientChannel, httpRequest, pipeline);
53 | return;
54 | }
55 | String contentLength = httpRequest.headers().get(HttpHeaderNames.CONTENT_LENGTH);
56 | //缓存request content
57 | if (contentLength != null) {
58 | content = PooledByteBufAllocator.DEFAULT.buffer();
59 | }
60 | pipeline.beforeRequest(clientChannel, HttpRequestInfo.adapter(httpRequest));
61 | }
62 |
63 | @Override
64 | public void beforeRequest(Channel clientChannel, HttpContent httpContent,
65 | HttpProxyInterceptPipeline pipeline) throws Exception {
66 | if (!matchFlag) {
67 | super.beforeRequest(clientChannel, httpContent, pipeline);
68 | return;
69 | }
70 | if (content != null) {
71 | ByteBuf temp = httpContent.content().slice();
72 | content.writeBytes(temp);
73 | if (httpContent instanceof LastHttpContent) {
74 | try {
75 | byte[] contentBts = new byte[content.readableBytes()];
76 | content.readBytes(contentBts);
77 | ((HttpRequestInfo) pipeline.getHttpRequest()).setContent(contentBts);
78 | } finally {
79 | ReferenceCountUtil.release(content);
80 | }
81 | }
82 | }
83 | pipeline.beforeRequest(clientChannel, httpContent);
84 | }
85 |
86 | @Override
87 | public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpResponse httpResponse,
88 | HttpProxyInterceptPipeline pipeline) throws Exception {
89 | if (!matchFlag) {
90 | super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
91 | return;
92 | }
93 | if ((httpResponse.status().code() + "").indexOf("20") == 0) { //响应码为20x
94 | HttpHeaders httpResHeaders = httpResponse.headers();
95 | String accept = pipeline.getHttpRequest().headers().get(HttpHeaderNames.ACCEPT);
96 | String contentType = httpResHeaders.get(HttpHeaderNames.CONTENT_TYPE);
97 | //有两种情况进行下载 1.url后缀为.xxx 2.带有CONTENT_DISPOSITION:ATTACHMENT响应头
98 | String disposition = httpResHeaders.get(HttpHeaderNames.CONTENT_DISPOSITION);
99 | if (accept != null
100 | && accept.matches("^.*text/html.*$")
101 | && ((disposition != null
102 | && disposition.contains(HttpHeaderValues.ATTACHMENT)
103 | && disposition.contains(HttpHeaderValues.FILENAME))
104 | || isDownContentType(contentType))) {
105 | downFlag = true;
106 | }
107 |
108 | HttpRequestInfo httpRequestInfo = (HttpRequestInfo) pipeline.getHttpRequest();
109 | if (downFlag) { //如果是下载
110 | LOGGER.debug("=====================下载===========================\n" +
111 | pipeline.getHttpRequest().toString() + "\n" +
112 | "------------------------------------------------" +
113 | httpResponse.toString() + "\n" +
114 | "================================================");
115 | proxyChannel.close();//关闭嗅探下载连接
116 | httpRequestInfo.setRequestProto(new RequestProto(pipeline.getRequestProto().getHost(), pipeline.getRequestProto().getPort(), pipeline.getRequestProto().getSsl()));
117 | HttpRequestForm requestForm = HttpRequestForm.parse(httpRequestInfo);
118 | HttpResponseInfo responseInfo = HttpDownUtil.getHttpResponseInfo(httpRequestInfo, null, null, (NioEventLoopGroup) clientChannel.eventLoop().parent());
119 | httpResponse.headers().clear();
120 | httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
121 | httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
122 | String js = "";
123 | HttpContent httpContent = new DefaultLastHttpContent();
124 | httpContent.content().writeBytes(js.getBytes());
125 | httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, httpContent.content().readableBytes());
126 | clientChannel.writeAndFlush(httpResponse);
127 | clientChannel.writeAndFlush(httpContent);
128 | clientChannel.close();
129 | ObjectMapper objectMapper = new ObjectMapper();
130 | String requestParam = URLEncoder.encode(objectMapper.writeValueAsString(requestForm), "utf-8");
131 | String responseParam = URLEncoder.encode(objectMapper.writeValueAsString(responseInfo), "utf-8");
132 | String uri = "/#/tasks?request=" + requestParam + "&response=" + responseParam;
133 | DownApplication.INSTANCE.loadUri(uri, false);
134 | return;
135 | } else {
136 | if (httpRequestInfo.content() != null) {
137 | httpRequestInfo.setContent(null);
138 | }
139 | }
140 | }
141 | super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
142 | }
143 |
144 | @Override
145 | public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpContent httpContent,
146 | HttpProxyInterceptPipeline pipeline) throws Exception {
147 | if (!matchFlag) {
148 | super.afterResponse(clientChannel, proxyChannel, httpContent, pipeline);
149 | return;
150 | }
151 | if (downFlag) {
152 | httpContent.release();
153 | } else {
154 | pipeline.afterResponse(clientChannel, proxyChannel, httpContent);
155 | }
156 | }
157 |
158 | //https://chromium.googlesource.com/chromium/src/+/master/net/base/mime_util.cc
159 | private static final String[] CONTENT_TYPES = {
160 | "application/javascript",
161 | "application/x-javascript",
162 | "application/wasm",
163 | "application/x-chrome-extension",
164 | "application/xhtml+xml",
165 | "application/font-woff",
166 | "application/json",
167 | "application/x-shockwave-flash",
168 | "audio/mpeg",
169 | "audio/flac",
170 | "audio/mp3",
171 | "audio/ogg",
172 | "audio/wav",
173 | "audio/webm",
174 | "audio/x-m4a",
175 | "image/gif",
176 | "image/jpeg",
177 | "image/png",
178 | "image/apng",
179 | "image/webp",
180 | "image/x-icon",
181 | "image/bmp",
182 | "image/jpeg",
183 | "image/svg+xml",
184 | "image/tiff",
185 | "image/vnd.microsoft.icon",
186 | "image/x-png",
187 | "image/x-xbitmap",
188 | "video/webm",
189 | "video/ogg",
190 | "video/mp4",
191 | "video/mpeg",
192 | "text/css",
193 | "text/html",
194 | "text/xml",
195 | "text/calendar",
196 | "text/html",
197 | "text/plain",
198 | "text/x-sh",
199 | "text/xml",
200 | "multipart/related",
201 | "message/rfc822",
202 | };
203 |
204 | private boolean isDownContentType(String contentType) {
205 | if (contentType != null) {
206 | String contentTypeFinal = contentType.split(";")[0].trim().toLowerCase();
207 | return Arrays.stream(CONTENT_TYPES).noneMatch(type -> contentTypeFinal.equals(type));
208 | }
209 | return true;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/server/PDownProxyServer.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.server;
2 |
3 | import com.github.monkeywie.proxyee.exception.HttpProxyExceptionHandle;
4 | import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptInitializer;
5 | import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
6 | import com.github.monkeywie.proxyee.server.HttpProxyServer;
7 | import com.github.monkeywie.proxyee.server.HttpProxyServerConfig;
8 | import io.netty.channel.Channel;
9 | import org.pdown.gui.content.PDownConfigContent;
10 | import org.pdown.gui.entity.PDownConfigInfo;
11 | import org.pdown.gui.extension.mitm.intercept.AjaxIntercept;
12 | import org.pdown.gui.extension.mitm.intercept.CookieIntercept;
13 | import org.pdown.gui.extension.mitm.intercept.ScriptIntercept;
14 | import org.pdown.gui.extension.mitm.intercept.SniffIntercept;
15 | import org.pdown.gui.extension.mitm.ssl.PDownCACertFactory;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 |
19 | public class PDownProxyServer {
20 |
21 | private static final Logger LOGGER = LoggerFactory.getLogger(PDownProxyServer.class);
22 |
23 | private static volatile HttpProxyServer httpProxyServer;
24 | public static volatile boolean isStart = false;
25 |
26 | public static void start(int port) {
27 | HttpProxyServerConfig config = new HttpProxyServerConfig();
28 | //处理ssl
29 | config.setHandleSsl(true);
30 | //线程池数量都设置为1
31 | config.setBossGroupThreads(1);
32 | config.setWorkerGroupThreads(1);
33 | config.setProxyGroupThreads(1);
34 | httpProxyServer = new HttpProxyServer()
35 | .serverConfig(config)
36 | .proxyConfig(PDownConfigInfo.convert(PDownConfigContent.getInstance().get().getProxyConfig()))
37 | .caCertFactory(new PDownCACertFactory())
38 | .proxyInterceptInitializer(new HttpProxyInterceptInitializer() {
39 | @Override
40 | public void init(HttpProxyInterceptPipeline pipeline) {
41 | pipeline.addLast(new CookieIntercept());
42 | pipeline.addLast(new AjaxIntercept());
43 | pipeline.addLast(new ScriptIntercept());
44 | pipeline.addLast(new SniffIntercept());
45 | }
46 | })
47 | .httpProxyExceptionHandle(new HttpProxyExceptionHandle() {
48 | @Override
49 | public void beforeCatch(Channel clientChannel, Throwable cause) throws Exception {
50 | LOGGER.warn("beforeCatch", cause);
51 | }
52 |
53 | @Override
54 | public void afterCatch(Channel clientChannel, Channel proxyChannel, Throwable cause) throws Exception {
55 | LOGGER.warn("afterCatch", cause);
56 | }
57 | });
58 | isStart = true;
59 | httpProxyServer.start(port);
60 | }
61 |
62 | public static void close() {
63 | if (httpProxyServer != null) {
64 | httpProxyServer.close();
65 | }
66 | isStart = false;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/ssl/PDownCACertFactory.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.ssl;
2 |
3 | import com.github.monkeywie.proxyee.crt.CertUtil;
4 | import com.github.monkeywie.proxyee.server.HttpProxyCACertFactory;
5 | import java.io.File;
6 | import java.security.PrivateKey;
7 | import java.security.cert.X509Certificate;
8 | import org.pdown.rest.util.PathUtil;
9 |
10 | public class PDownCACertFactory implements HttpProxyCACertFactory {
11 |
12 | private static final String SSL_PATH = PathUtil.ROOT_PATH + File.separator + "ssl" + File.separator;
13 |
14 | @Override
15 | public X509Certificate getCACert() throws Exception {
16 | return CertUtil.loadCert(SSL_PATH + "ca.crt");
17 | }
18 |
19 | @Override
20 | public PrivateKey getCAPriKey() throws Exception {
21 | return CertUtil.loadPriKey(SSL_PATH + ".ca_pri.der");
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionCertUtil.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.util;
2 |
3 | import com.github.monkeywie.proxyee.crt.CertUtil;
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.net.URLEncoder;
7 | import java.nio.file.Files;
8 | import java.nio.file.Paths;
9 | import java.security.KeyPair;
10 | import java.security.MessageDigest;
11 | import java.security.cert.X509Certificate;
12 | import java.util.Date;
13 | import java.util.concurrent.TimeUnit;
14 | import java.util.regex.Matcher;
15 | import java.util.regex.Pattern;
16 | import org.pdown.core.util.FileUtil;
17 | import org.pdown.core.util.OsUtil;
18 | import org.pdown.gui.DownApplication;
19 | import org.pdown.gui.util.ExecUtil;
20 | import sun.security.x509.X500Name;
21 |
22 | /**
23 | * 用于处理系统的证书安装、查询和卸载
24 | */
25 | public class ExtensionCertUtil {
26 |
27 |
28 | /**
29 | * 在指定目录生成一个ca证书和私钥
30 | */
31 | public static void buildCert(String path, String subjectName) throws Exception {
32 | //生成ca证书和私钥
33 | KeyPair keyPair = CertUtil.genKeyPair();
34 | File priKeyFile = FileUtil.createFile(path + File.separator + ".ca_pri.der", true);
35 | File caCertFile = FileUtil.createFile(path + File.separator + "ca.crt", false);
36 | Files.write(Paths.get(priKeyFile.toURI()), keyPair.getPrivate().getEncoded());
37 | Files.write(Paths.get(caCertFile.toURI()),
38 | CertUtil.genCACert(
39 | "C=CN, ST=GD, L=SZ, O=lee, OU=study, CN=" + subjectName,
40 | new Date(),
41 | new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(3650)),
42 | keyPair)
43 | .getEncoded());
44 | }
45 |
46 | /**
47 | * 安装证书
48 | */
49 | public static void installCert(File file) throws IOException {
50 | String path = file.getPath();
51 | if (OsUtil.isWindows()) {
52 | ExecUtil.execBlock("certutil",
53 | "-addstore",
54 | "-user",
55 | "root",
56 | path);
57 | } else if (OsUtil.isMac()) {
58 | ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/cert/install?path=" + URLEncoder.encode(path, "utf-8"));
59 | }
60 | }
61 |
62 | /**
63 | * 通过证书subjectName和sha1,判断系统是否已安装该证书
64 | */
65 | public static boolean isInstalledCert(File file) throws Exception {
66 | if (!file.exists()) {
67 | return false;
68 | }
69 | if (OsUtil.isUnix()) {
70 | return true;
71 | }
72 | X509Certificate cert = CertUtil.loadCert(file.toURI());
73 | String subjectName = ((X500Name) cert.getSubjectDN()).getCommonName();
74 | String sha1 = getCertSHA1(cert);
75 | return findCertList(subjectName).toUpperCase().replaceAll("\\s", "").indexOf(":" + sha1.toUpperCase()) != -1;
76 | }
77 |
78 | /**
79 | * 通过证书subjectName,判断系统是否已安装此subjectName的证书
80 | */
81 | public static boolean existsCert(String subjectName) throws IOException {
82 | if (OsUtil.isWindows() && findCertList(subjectName).toUpperCase().indexOf("=====") != -1) {
83 | return true;
84 | } else if (OsUtil.isMac() && findCertList(subjectName).toUpperCase().indexOf("BEGIN CERTIFICATE") != -1) {
85 | return true;
86 | }
87 | return false;
88 | }
89 |
90 | /**
91 | * 通过证书name,卸载证书
92 | */
93 | public static void uninstallCert(String subjectName) throws IOException {
94 | if (OsUtil.isWindows()) {
95 | Pattern pattern = Pattern.compile("(?i)\\(sha1\\):\\s(.*)\r?\n");
96 | String certList = findCertList(subjectName);
97 | Matcher matcher = pattern.matcher(certList);
98 | while (matcher.find()) {
99 | String hash = matcher.group(1).replaceAll("\\s", "");
100 | ExecUtil.execBlock("certutil",
101 | "-delstore",
102 | "-user",
103 | "root",
104 | hash);
105 | }
106 | } else if (OsUtil.isMac()) {
107 | String certList = findCertList(subjectName);
108 | Pattern pattern = Pattern.compile("(?i)SHA-1 hash:\\s(.*)\r?\n");
109 | Matcher matcher = pattern.matcher(certList);
110 | while (matcher.find()) {
111 | String hash = matcher.group(1);
112 | ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/cert/uninstall"
113 | + "?hash=" + hash);
114 | }
115 | }
116 | }
117 |
118 | //查询证书列表
119 | private static String findCertList(String subjectName) throws IOException {
120 | if (OsUtil.isWindows()) {
121 | return ExecUtil.exec("certutil ",
122 | "-store",
123 | "-user",
124 | "root",
125 | subjectName);
126 | } else if (OsUtil.isMac()) {
127 | return ExecUtil.exec("security",
128 | "find-certificate",
129 | "-a",
130 | "-c",
131 | subjectName,
132 | "-p",
133 | "-Z",
134 | "/Library/Keychains/System.keychain");
135 | }
136 | return null;
137 | }
138 |
139 | private static String getCertSHA1(X509Certificate certificate) throws Exception {
140 | MessageDigest md = MessageDigest.getInstance("SHA-1");
141 | byte[] der = certificate.getEncoded();
142 | md.update(der);
143 | return btsToHex(md.digest());
144 | }
145 |
146 | private static String btsToHex(byte[] bts) {
147 | StringBuilder str = new StringBuilder();
148 | for (byte b : bts) {
149 | str.append(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(" ", "0"));
150 | }
151 | return str.toString();
152 | }
153 |
154 | public static void main(String[] args) throws Exception {
155 | String subjectName = "ProxyeeDown CA";
156 | String path = "f:/test/";
157 | File certFile = new File(path + "ca.crt");
158 | //证书还未安装
159 | if (!isInstalledCert(certFile)) {
160 | if (existsCert(subjectName)) {
161 | //存在无用证书需要卸载
162 | uninstallCert(subjectName);
163 | }
164 | //生成新的证书
165 | buildCert(path, subjectName);
166 | //安装
167 | installCert(certFile);
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionProxyUtil.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.extension.mitm.util;
2 |
3 | import com.sun.jna.Pointer;
4 | import java.io.IOException;
5 | import java.net.InetAddress;
6 | import java.net.NetworkInterface;
7 | import java.net.Socket;
8 | import java.net.SocketException;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.Enumeration;
12 | import java.util.HashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.Map.Entry;
16 | import java.util.regex.Matcher;
17 | import java.util.regex.Pattern;
18 | import org.pdown.core.util.OsUtil;
19 | import org.pdown.gui.DownApplication;
20 | import org.pdown.gui.extension.mitm.util.WinInet.INTERNET_PER_CONN_OPTION;
21 | import org.pdown.gui.extension.mitm.util.WinInet.INTERNET_PER_CONN_OPTION.ByReference;
22 | import org.pdown.gui.extension.mitm.util.WinInet.INTERNET_PER_CONN_OPTION_LIST;
23 | import org.pdown.gui.util.ExecUtil;
24 |
25 | /**
26 | * 系统的代理切换工具类
27 | */
28 | public class ExtensionProxyUtil {
29 |
30 | /**
31 | * 设置PAC代理
32 | */
33 | public static void enabledPACProxy(String url) throws IOException {
34 | if (OsUtil.isWindows()) {
35 | String interName = getRemoteInterface();
36 | INTERNET_PER_CONN_OPTION_LIST list = buildOptionList(interName, 2);
37 | INTERNET_PER_CONN_OPTION[] pOptions = (INTERNET_PER_CONN_OPTION[]) list.pOptions
38 | .toArray(list.dwOptionCount);
39 | // Set flags.
40 | pOptions[0].dwOption = WinInet.INTERNET_PER_CONN_FLAGS;
41 | pOptions[0].Value.dwValue = WinInet.PROXY_TYPE_AUTO_PROXY_URL;
42 | pOptions[0].Value.setType(int.class);
43 |
44 | // Set flags.
45 | pOptions[1].dwOption = WinInet.INTERNET_PER_CONN_AUTOCONFIG_URL;
46 | pOptions[1].Value.pszValue = url;
47 | pOptions[1].Value.setType(String.class);
48 |
49 | refreshOptions(list);
50 | } else if (OsUtil.isMac()) {
51 | String networkService = disabledProxy();
52 | ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/proxy/enabledPAC"
53 | + "?ns=" + networkService
54 | + "&url=" + url);
55 | }
56 | }
57 |
58 | /**
59 | * 启用http代理
60 | */
61 | public static void enabledHTTPProxy(String host, int port) throws IOException {
62 | if (OsUtil.isWindows()) {
63 | String interName = getRemoteInterface();
64 | INTERNET_PER_CONN_OPTION_LIST list = buildOptionList(interName, 2);
65 | INTERNET_PER_CONN_OPTION[] pOptions = (INTERNET_PER_CONN_OPTION[]) list.pOptions
66 | .toArray(list.dwOptionCount);
67 |
68 | // Set flags.
69 | pOptions[0].dwOption = WinInet.INTERNET_PER_CONN_FLAGS;
70 | pOptions[0].Value.dwValue = WinInet.PROXY_TYPE_PROXY;
71 | pOptions[0].Value.setType(int.class);
72 |
73 | // Set proxy name.
74 | pOptions[1].dwOption = WinInet.INTERNET_PER_CONN_PROXY_SERVER;
75 | pOptions[1].Value.pszValue = host + ":" + port;
76 | pOptions[1].Value.setType(String.class);
77 |
78 | refreshOptions(list);
79 | } else if (OsUtil.isMac()) {
80 | String networkService = disabledProxy();
81 | ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/proxy/enabledHTTP"
82 | + "?ns=" + networkService
83 | + "&host=" + host
84 | + "&port=" + port);
85 | }
86 | }
87 |
88 | /**
89 | * 禁用代理
90 | */
91 | public static String disabledProxy() throws IOException {
92 | if (OsUtil.isWindows()) {
93 | String interName = getRemoteInterface();
94 | INTERNET_PER_CONN_OPTION_LIST list = buildOptionList(interName, 1);
95 | INTERNET_PER_CONN_OPTION[] pOptions = (INTERNET_PER_CONN_OPTION[]) list.pOptions
96 | .toArray(list.dwOptionCount);
97 | // Set flags.
98 | pOptions[0].dwOption = WinInet.INTERNET_PER_CONN_FLAGS;
99 | pOptions[0].Value.dwValue = WinInet.PROXY_TYPE_DIRECT;
100 | pOptions[0].Value.setType(int.class);
101 |
102 | refreshOptions(list);
103 | } else if (OsUtil.isMac()) {
104 | String networkService = getRemoteInterface();
105 | ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/proxy/disabled"
106 | + "?ns=" + networkService);
107 | return networkService;
108 | }
109 | return null;
110 | }
111 |
112 | /**
113 | * 获取访问外网使用的网卡
114 | */
115 | private static String getRemoteInterface() throws IOException {
116 | Map> interfacesInfo = getInterfacesInfo();
117 | Socket socket = new Socket("www.baidu.com", 80);
118 | for (Entry> entry : interfacesInfo.entrySet()) {
119 | if (entry.getValue().contains(socket.getLocalAddress().getHostAddress())) {
120 | String remoteInterface = entry.getKey();
121 | if (OsUtil.isWindows()) {
122 | try {
123 | String result = ExecUtil.exec("rasdial");
124 | if (result != null && Arrays.stream(result.split("\r\n")).anyMatch(line -> line.equals(remoteInterface))) {
125 | return remoteInterface;
126 | }
127 | } catch (IOException e) {
128 | return null;
129 | }
130 | } else if (OsUtil.isMac()) {
131 | String result = ExecUtil.exec("networksetup", "-listnetworkserviceorder");
132 | Pattern pattern = Pattern.compile("\\(Hardware\\sPort:\\s(.*),\\sDevice:\\s(.*)\\)");
133 | Matcher matcher = pattern.matcher(result);
134 | while (matcher.find()) {
135 | if (matcher.group(2).equalsIgnoreCase(remoteInterface)) {
136 | return matcher.group(1);
137 | }
138 | }
139 | }
140 | }
141 | }
142 | return null;
143 | }
144 |
145 | /**
146 | * 获取本机所有网卡
147 | */
148 | public static Map> getInterfacesInfo() throws SocketException {
149 | Map> interfacesInfo = new HashMap<>();
150 | Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
151 | while (interfaces.hasMoreElements()) {
152 | NetworkInterface networkInterface = interfaces.nextElement();
153 | Enumeration addresses = networkInterface.getInetAddresses();
154 | while (addresses.hasMoreElements()) {
155 | InetAddress nextElement = addresses.nextElement();
156 | String name = networkInterface.getDisplayName();
157 | List ipList = interfacesInfo.get(name);
158 | if (ipList == null) {
159 | ipList = new ArrayList<>();
160 | interfacesInfo.put(name, ipList);
161 | }
162 | ipList.add(nextElement.getHostAddress());
163 | }
164 | }
165 | return interfacesInfo;
166 | }
167 |
168 | public static void main(String[] args) throws Exception {
169 | System.out.println(getRemoteInterface());
170 | }
171 |
172 | private static INTERNET_PER_CONN_OPTION_LIST buildOptionList(String connectionName, int size) {
173 | INTERNET_PER_CONN_OPTION_LIST list = new INTERNET_PER_CONN_OPTION_LIST();
174 | // Fill the list structure.
175 | list.dwSize = list.size();
176 |
177 | // NULL == LAN, otherwise connectoid name.
178 | list.pszConnection = connectionName;
179 |
180 | // Set three options.
181 | list.dwOptionCount = size;
182 | list.pOptions = new ByReference();
183 |
184 | // Ensure that the memory was allocated.
185 | if (null == list.pOptions) {
186 | // Return FALSE if the memory wasn't allocated.
187 | return null;
188 | }
189 | return list;
190 | }
191 |
192 | private static boolean refreshOptions(INTERNET_PER_CONN_OPTION_LIST list) {
193 | if (!WinInet.INSTANCE
194 | .InternetSetOption(Pointer.NULL, WinInet.INTERNET_OPTION_PER_CONNECTION_OPTION, list,
195 | list.size())) {
196 | return false;
197 | }
198 |
199 | if (!WinInet.INSTANCE
200 | .InternetSetOption(Pointer.NULL, WinInet.INTERNET_OPTION_PROXY_SETTINGS_CHANGED,
201 | Pointer.NULL, 0)) {
202 | return false;
203 | }
204 |
205 | // Refresh Internet Options
206 | if (!WinInet.INSTANCE
207 | .InternetSetOption(Pointer.NULL, WinInet.INTERNET_OPTION_REFRESH, Pointer.NULL, 0)) {
208 | return false;
209 | }
210 | return true;
211 | }
212 | }
213 |
214 |
--------------------------------------------------------------------------------
/main/src/main/java/org/pdown/gui/http/EmbedHttpServer.java:
--------------------------------------------------------------------------------
1 | package org.pdown.gui.http;
2 |
3 | import io.netty.bootstrap.ServerBootstrap;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelFuture;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.ChannelInitializer;
8 | import io.netty.channel.SimpleChannelInboundHandler;
9 | import io.netty.channel.nio.NioEventLoopGroup;
10 | import io.netty.channel.socket.nio.NioServerSocketChannel;
11 | import io.netty.handler.codec.http.FullHttpRequest;
12 | import io.netty.handler.codec.http.FullHttpResponse;
13 | import io.netty.handler.codec.http.HttpHeaderNames;
14 | import io.netty.handler.codec.http.HttpHeaderValues;
15 | import io.netty.handler.codec.http.HttpObjectAggregator;
16 | import io.netty.handler.codec.http.HttpResponseStatus;
17 | import io.netty.handler.codec.http.HttpServerCodec;
18 | import io.netty.util.concurrent.GenericFutureListener;
19 | import java.lang.reflect.Method;
20 | import java.net.URI;
21 | import java.util.ArrayList;
22 | import java.util.HashMap;
23 | import java.util.List;
24 | import java.util.Map;
25 | import org.pdown.gui.http.controller.DefaultController;
26 | import org.pdown.gui.http.controller.NativeController;
27 | import org.pdown.gui.http.util.HttpHandlerUtil;
28 | import org.slf4j.Logger;
29 | import org.slf4j.LoggerFactory;
30 | import org.springframework.web.bind.annotation.RequestMapping;
31 |
32 | public class EmbedHttpServer {
33 |
34 | private static final Logger LOGGER = LoggerFactory.getLogger(EmbedHttpServer.class);
35 |
36 | private int port;
37 | private DefaultController defaultController;
38 | private List