Skip to content

Commit

Permalink
feature: 拦截配置功能,允许匹配到多个 ‘域名匹配符’ 下的拦截配置了,只要域名符合 ‘域名匹配符’。 (#286)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangliang181230 authored Mar 29, 2024
1 parent 3ade87d commit 74c1473
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 86 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function doDiff (oldObj, newObj) {

function deleteNullItems (target) {
lodash.forEach(target, (item, key) => {
if (item == null) {
if (item == null || item === '[delete]') {
delete target[key]
}
if (lodash.isObject(item)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/gui/src/view/components/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
:style="{ height: '100%' }"
>
<a-tab-pane tab="拦截设置" key="1" >
<vue-json-editor style="height:100%;" ref="editor" v-model="targetConfig.intercepts" mode="code" :show-btns="false" :expandedOnStart="true" @json-change="onJsonChange" ></vue-json-editor>
<vue-json-editor style="height:100%;" ref="editor" v-model="targetConfig.intercepts" mode="code" :show-btns="false" :expandedOnStart="true" @json-change="onJsonChange"></vue-json-editor>
</a-tab-pane>
<a-tab-pane tab="DNS设置" key="2">
<div>
Expand Down
2 changes: 0 additions & 2 deletions packages/gui/src/view/pages/server.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@
<!-- <a-button type="danger" icon="minus" @click="deleteSniList(item,index)"/>-->
<!-- </a-col>-->
<!-- </a-row>-->

<!-- </a-tab-pane>-->
<a-tab-pane tab="IP测速" key="6">
<div>
Expand Down Expand Up @@ -201,7 +200,6 @@
</a-card>
</a-col>
</a-row>

</div>
</a-tab-pane>
</a-tabs>
Expand Down
4 changes: 3 additions & 1 deletion packages/mitmproxy/src/lib/interceptor/impl/req/OPTIONS.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ function readConfig (config, defaultConfig) {
}

module.exports = {
name: 'options',
priority: 1,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context

Expand All @@ -27,7 +29,7 @@ module.exports = {

const headers = {
// 允许跨域
'Dev-Sidecar-Interceptor': 'options',
'DS-Interceptor': 'options',
'Access-Control-Allow-Origin': rOptions.headers.origin,
'Access-Control-Allow-Headers': allowHeaders,
'Access-Control-Allow-Methods': allowMethods,
Expand Down
22 changes: 17 additions & 5 deletions packages/mitmproxy/src/lib/interceptor/impl/req/abort.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
module.exports = {
requestIntercept (context, interceptOpts, req, res, ssl, next) {
name: 'abort',
priority: 103,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
log.info('abort:', rOptions.hostname, req.url)
res.writeHead(403)
res.write('DevSidecar 403: \n\n request abort, this request is matched by abort intercept.\n\n 因配置abort拦截器,本请求将取消')

res.writeHead(403, {
'Content-Type': 'text/plain; charset=utf-8',
'DS-Interceptor': 'abort'
})
res.write(
'DevSidecar 403: Request abort.\r\n\r\n' +
' This request is matched by abort intercept.\r\n' +
' 因配置abort拦截器,本请求直接返回403禁止访问。'
)
res.end()
return true// 是否结束

const url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('abort intercept:', url)
return true // true代表请求结束
},
is (interceptOpt) {
return !!interceptOpt.abort
Expand Down
10 changes: 7 additions & 3 deletions packages/mitmproxy/src/lib/interceptor/impl/req/cacheReq.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ function getLastModifiedTimeFromIfModifiedSince (rOptions, log) {
}

module.exports = {
name: 'cacheReq',
priority: 111,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context

Expand All @@ -65,12 +67,12 @@ module.exports = {
// 获取 Cache-Control 用于判断是否禁用缓存
const cacheControl = rOptions.headers['cache-control']
if (cacheControl && (cacheControl.indexOf('no-cache') >= 0 || cacheControl.indexOf('no-store') >= 0)) {
return // 禁用缓存,跳过当前拦截器
return // 当前请求指定要禁用缓存,跳过当前拦截器
}
// 获取 Pragma 用于判断是否禁用缓存
const pragma = rOptions.headers.pragma
if (pragma && (pragma.indexOf('no-cache') >= 0 || pragma.indexOf('no-store') >= 0)) {
return // 禁用缓存,跳过当前拦截
return // 当前请求指定要禁用缓存,跳过当前拦截器
}

// 最近编辑时间
Expand All @@ -89,10 +91,12 @@ module.exports = {

// 缓存未过期,直接拦截请求并响应304
res.writeHead(304, {
'Dev-Sidecar-Interceptor': 'cacheReq'
'DS-Interceptor': 'cache: ' + maxAge
})
res.end()

const url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('cache intercept:', url)
return true
},
is (interceptOpt) {
Expand Down
38 changes: 25 additions & 13 deletions packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const url = require('url')
const lodash = require('lodash')
module.exports = {
name: 'proxy',
priority: 121,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log, RequestCounter } = context

Expand All @@ -8,17 +11,17 @@ module.exports = {
let proxyConf = interceptOpt.proxy
if (RequestCounter && interceptOpt.backup && interceptOpt.backup.length > 0) {
// 优选逻辑
const backup = [proxyConf]
const backupList = [proxyConf]
for (const bk of interceptOpt.backup) {
backup.push(bk)
backupList.push(bk)
}
const key = rOptions.hostname + '/' + interceptOpt.key
const count = RequestCounter.getOrCreate(key, backup)
const count = RequestCounter.getOrCreate(key, backupList)
if (count.value == null) {
count.doRank()
}
if (count.value == null) {
log.error('count value is null', count)
log.error('`count.value` is null, the count:', count)
} else {
count.doCount(count.value)
proxyConf = count.value
Expand All @@ -30,27 +33,34 @@ module.exports = {
}
}

let uri = req.url
if (uri.indexOf('http') === 0) {
// eslint-disable-next-line node/no-deprecated-api
const URL = url.parse(uri)
uri = URL.path
}
let proxyTarget = proxyConf + uri
// 获取代理目标地址
let proxyTarget
if (interceptOpt.replace) {
const regexp = new RegExp(interceptOpt.replace)
proxyTarget = req.url.replace(regexp, proxyConf)
} else if (proxyConf.indexOf('http:') === 0 || proxyConf.indexOf('https:') === 0) {
proxyTarget = proxyConf
} else {
let uri = req.url
if (uri.indexOf('http') === 0) {
// eslint-disable-next-line node/no-deprecated-api
const URL = url.parse(uri)
uri = URL.path
}
proxyTarget = proxyConf + uri
}

// eslint-disable-next-line
// no-template-curly-in-string
// eslint-disable-next-line no-template-curly-in-string
proxyTarget = proxyTarget.replace('${host}', rOptions.hostname)

log.info('拦截【proxy】: original:', rOptions.hostname, ',target:', proxyTarget)
// const backup = interceptOpt.backup
const proxy = proxyTarget.indexOf('http') === 0 ? proxyTarget : rOptions.protocol + '//' + proxyTarget
// eslint-disable-next-line node/no-deprecated-api
const URL = url.parse(proxy)
rOptions.origional = lodash.cloneDeep(rOptions) // 备份原始请求参数
delete rOptions.origional.agent
delete rOptions.origional.headers
rOptions.protocol = URL.protocol
rOptions.hostname = URL.host
rOptions.host = URL.host
Expand All @@ -69,8 +79,10 @@ module.exports = {
if (rOptions.agent && rOptions.agent.options) {
rOptions.agent.options.rejectUnauthorized = false
}
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}, sni: ${interceptOpt.sni}`)
log.info('proxy intercept: hostname:', originHostname, ', target:', proxyTarget, ', sni replace servername:', rOptions.servername)
} else {
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}`)
log.info('proxy intercept: hostname:', originHostname, ', target:', proxyTarget)
}

Expand Down
29 changes: 23 additions & 6 deletions packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
module.exports = {
name: 'redirect',
priority: 102,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
const url = req.url

let redirect
if (typeof interceptOpt.redirect === 'string') {
redirect = rOptions.protocol + '//' + interceptOpt.redirect + url
if (interceptOpt.redirect.indexOf('http:') === 0 || interceptOpt.redirect.indexOf('https:') === 0) {
redirect = interceptOpt.redirect
} else {
redirect = rOptions.protocol + '//' + interceptOpt.redirect + req.url
}
} else {
redirect = interceptOpt.redirect(url)
redirect = interceptOpt.redirect(req.url)
}
log.info('请求重定向:', rOptions.hostname, url, redirect)
res.writeHead(302, { Location: redirect })

// eslint-disable-next-line
// no-template-curly-in-string
// eslint-disable-next-line no-template-curly-in-string
redirect = redirect.replace('${host}', rOptions.hostname)

res.writeHead(302, {
Location: redirect,
'DS-Interceptor': 'redirect'
})
res.end()
return true// 是否结束

const url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info(`redirect intercept: ${url}${redirect}`)
return true // true代表请求结束
},
is (interceptOpt) {
return interceptOpt.redirect // 如果配置中有redirect,那么这个配置是需要redirect拦截的
Expand Down
18 changes: 11 additions & 7 deletions packages/mitmproxy/src/lib/interceptor/impl/req/sni.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
module.exports = {
requestIntercept (context, interceptOpt) {
name: 'sni',
priority: 122,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
if (interceptOpt.sni != null) {
rOptions.servername = interceptOpt.sni
if (rOptions.agent && rOptions.agent.options) {
rOptions.agent.options.rejectUnauthorized = false
}
log.info('sni intercept: sni replace servername:', rOptions.hostname, '➜', rOptions.servername)

rOptions.servername = interceptOpt.sni
if (rOptions.agent && rOptions.agent.options) {
rOptions.agent.options.rejectUnauthorized = false
}

res.setHeader('DS-Interceptor', 'sni: ' + interceptOpt.sni)

log.info('sni intercept: sni replace servername:', rOptions.hostname, '➜', rOptions.servername)
return true
},
is (interceptOpt) {
Expand Down
22 changes: 17 additions & 5 deletions packages/mitmproxy/src/lib/interceptor/impl/req/success.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
module.exports = {
requestIntercept (context, interceptOpts, req, res, ssl, next) {
name: 'success',
priority: 101,
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
log.info('success:', rOptions.hostname, req.url)
res.writeHead(200)
res.write('DevSidecar 200: \n\n request success, this request is matched by success intercept.\n\n 因配置success拦截器,本请求将直接返回成功')

res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
'DS-Interceptor': 'success'
})
res.write(
'DevSidecar 200: Request success.\n\n' +
' This request is matched by success intercept.\n\n' +
' 因配置success拦截器,本请求直接返回200成功。'
)
res.end()
return true// 是否结束

const url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
log.info('success intercept:', url)
return true // true代表请求结束
},
is (interceptOpt) {
return !!interceptOpt.success
Expand Down
8 changes: 7 additions & 1 deletion packages/mitmproxy/src/lib/interceptor/impl/res/cacheRes.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
const cacheReq = require('../req/cacheReq')

module.exports = {
name: 'cacheRes',
priority: 201,
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log } = context

// 只有GET请求,且响应码为2xx时才进行缓存
if (rOptions.method !== 'GET' || proxyRes.statusCode < 200 || proxyRes.statusCode >= 300) {
// res.setHeader('DS-Cache-Interceptor', `skip: 'method' or 'status' not match`)
return
}

Expand Down Expand Up @@ -48,6 +51,9 @@ module.exports = {
if (interceptOpt.cacheImmutable !== false && originalHeaders.cacheControl.value.indexOf('immutable') < 0) {
maxAge = maxAgeMatch[1]
} else {
const url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
res.setHeader('DS-Cache-Interceptor', `skip: ${maxAgeMatch[1]} > ${maxAge}`)
log.info(`cache response intercept: skip: ${maxAgeMatch[1]} > ${maxAge}, url: ${url}`)
return
}
}
Expand Down Expand Up @@ -80,7 +86,7 @@ module.exports = {
res.setHeader('Expires', replaceHeaders.expires)
}

res.setHeader('Dev-Sidecar-Cache-Response-Interceptor', 'cacheRes:maxAge=' + maxAge)
res.setHeader('DS-Cache-Response-Interceptor', maxAge)
},
is (interceptOpt) {
const maxAge = cacheReq.getMaxAge(interceptOpt)
Expand Down
11 changes: 8 additions & 3 deletions packages/mitmproxy/src/lib/interceptor/impl/res/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,32 @@ function getScript (key, script) {
}

module.exports = {
name: 'script',
priority: 202,
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log, setting } = context
let keys = interceptOpt.script
if (typeof keys === 'string') {
keys = [keys]
}
try {
let tags = getScript('global', monkey.get(setting.script.dirAbsolutePath).global.script)
const scripts = monkey.get(setting.script.dirAbsolutePath)
let tags = getScript('global', scripts.global.script)
for (const key of keys) {
const script = monkey.get(setting.script.dirAbsolutePath)[key]
const script = scripts[key]
if (script == null) {
continue
}
const scriptTag = getScript(key, script.script)
tags += '\r\n' + scriptTag
}
log.info('responseIntercept: insert script', rOptions.hostname, rOptions.path)
res.setHeader('DS-Script-Interceptor', 'true')
log.info('script response intercept: insert script', rOptions.hostname, rOptions.path, ', head:', tags)
return {
head: tags
}
} catch (err) {
res.setHeader('DS-Script-Interceptor', 'error')
log.error('load monkey script error', err)
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/mitmproxy/src/lib/proxy/middleware/overwall.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function matched (hostname, overWallTargetMap) {
log.info(`matchHostname: matched overwall: '${hostname}' -> '${ret}' in pac.txt`)
return true
} else {
// log.debug(`matchHostname: matched overwall: Not-Matched '${hostname}' -> '${ret}' in pac.txt`)
log.debug(`matchHostname: matched overwall: Not-Matched '${hostname}' -> '${ret}' in pac.txt`)
return false
}
}
Expand Down
Loading

0 comments on commit 74c1473

Please sign in to comment.