2023-05-04 11:29:40 +08:00
|
|
|
|
package common
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"compress/gzip"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httputil"
|
|
|
|
|
"net/url"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/andybalholm/brotli"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
BING_CHAT_DOMAIN = "https://sydney.bing.com"
|
|
|
|
|
BING_CHAT_URL, _ = url.Parse(BING_CHAT_DOMAIN + "/sydney/ChatHub")
|
|
|
|
|
BING_URL, _ = url.Parse("https://www.bing.com")
|
|
|
|
|
KEEP_HEADERS = map[string]bool{
|
|
|
|
|
"Accept": true,
|
|
|
|
|
"Accept-Encoding": true,
|
|
|
|
|
"Accept-Language": true,
|
|
|
|
|
"Referer": true,
|
|
|
|
|
"Connection": true,
|
|
|
|
|
"Cookie": true,
|
|
|
|
|
"Upgrade": true,
|
|
|
|
|
"User-Agent": true,
|
|
|
|
|
"Sec-Websocket-Extensions": true,
|
|
|
|
|
"Sec-Websocket-Key": true,
|
|
|
|
|
"Sec-Websocket-Version": true,
|
|
|
|
|
"X-Request-Id": true,
|
|
|
|
|
"X-Forwarded-For": true,
|
|
|
|
|
}
|
2023-05-06 11:08:57 +08:00
|
|
|
|
DEL_LOCATION_DOMAINS = []string{
|
|
|
|
|
"https://cn.bing.com",
|
|
|
|
|
"https://www.bing.com",
|
|
|
|
|
}
|
|
|
|
|
RAND_IP_COOKIE_NAME = "BingAI_Rand_IP"
|
2023-05-04 11:29:40 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
|
|
|
|
|
originalScheme := "http"
|
|
|
|
|
httpsSchemeName := "https"
|
|
|
|
|
var originalHost string
|
|
|
|
|
var originalPath string
|
2023-05-05 14:04:06 +08:00
|
|
|
|
var originalDomain string
|
2023-05-06 11:08:57 +08:00
|
|
|
|
var randIP string
|
2023-05-04 11:29:40 +08:00
|
|
|
|
director := func(req *http.Request) {
|
|
|
|
|
if req.URL.Scheme == httpsSchemeName || req.Header.Get("X-Forwarded-Proto") == httpsSchemeName {
|
|
|
|
|
originalScheme = httpsSchemeName
|
|
|
|
|
}
|
|
|
|
|
originalHost = req.Host
|
|
|
|
|
originalPath = req.URL.Path
|
2023-05-05 14:04:06 +08:00
|
|
|
|
originalDomain = fmt.Sprintf("%s:%s", originalScheme, originalHost)
|
2023-05-04 11:29:40 +08:00
|
|
|
|
|
|
|
|
|
req.URL.Scheme = target.Scheme
|
|
|
|
|
req.URL.Host = target.Host
|
|
|
|
|
req.Host = target.Host
|
|
|
|
|
|
2023-05-05 14:04:06 +08:00
|
|
|
|
originalRefer := req.Referer()
|
|
|
|
|
if originalRefer != "" && !strings.Contains(originalRefer, "/web/chat.html") {
|
|
|
|
|
req.Header.Set("Referer", strings.ReplaceAll(originalRefer, originalDomain, BING_URL.String()))
|
|
|
|
|
} else {
|
|
|
|
|
req.Header.Set("Referer", fmt.Sprintf("%s/search?q=Bing+AI", BING_URL.String()))
|
|
|
|
|
}
|
2023-05-04 11:29:40 +08:00
|
|
|
|
|
2023-05-06 11:08:57 +08:00
|
|
|
|
// 同一会话尽量保持相同的随机IP
|
|
|
|
|
ckRandIP, _ := req.Cookie(RAND_IP_COOKIE_NAME)
|
|
|
|
|
if ckRandIP != nil && ckRandIP.Value != "" {
|
|
|
|
|
randIP = ckRandIP.Value
|
|
|
|
|
}
|
|
|
|
|
if randIP == "" {
|
|
|
|
|
randIP = GetRandomIP()
|
|
|
|
|
}
|
|
|
|
|
req.Header.Set("X-Forwarded-For", randIP)
|
2023-05-04 11:29:40 +08:00
|
|
|
|
|
|
|
|
|
for hKey, _ := range req.Header {
|
|
|
|
|
if _, isExist := KEEP_HEADERS[hKey]; !isExist {
|
|
|
|
|
req.Header.Del(hKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reqHeaderByte, _ := json.Marshal(req.Header)
|
|
|
|
|
// log.Println("剩余请求头 : ", string(reqHeaderByte))
|
|
|
|
|
}
|
|
|
|
|
//改写返回信息
|
|
|
|
|
modifyFunc := func(res *http.Response) error {
|
|
|
|
|
contentType := res.Header.Get("Content-Type")
|
|
|
|
|
if strings.Contains(contentType, "text/javascript") {
|
|
|
|
|
contentEncoding := res.Header.Get("Content-Encoding")
|
|
|
|
|
switch contentEncoding {
|
|
|
|
|
case "gzip":
|
|
|
|
|
// log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath)
|
|
|
|
|
modifyGzipBody(res, originalScheme, originalHost)
|
|
|
|
|
case "br":
|
|
|
|
|
// log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath)
|
|
|
|
|
modifyBrBody(res, originalScheme, originalHost)
|
|
|
|
|
default:
|
|
|
|
|
log.Println("ContentEncoding default : ", contentEncoding, " Path : ", originalPath)
|
|
|
|
|
modifyDefaultBody(res, originalScheme, originalHost)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 修改响应 cookie 域
|
|
|
|
|
// resCookies := res.Header.Values("Set-Cookie")
|
|
|
|
|
// if len(resCookies) > 0 {
|
|
|
|
|
// for i, v := range resCookies {
|
|
|
|
|
// resCookies[i] = strings.ReplaceAll(strings.ReplaceAll(v, ".bing.com", originalHost), "bing.com", originalHost)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2023-05-06 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
// 设置随机ip cookie
|
|
|
|
|
ckRandIP := &http.Cookie{
|
|
|
|
|
Name: RAND_IP_COOKIE_NAME,
|
|
|
|
|
Value: randIP,
|
|
|
|
|
Path: "/",
|
|
|
|
|
}
|
|
|
|
|
res.Header.Set("Set-Cookie", ckRandIP.String())
|
2023-05-04 11:29:40 +08:00
|
|
|
|
|
2023-05-05 14:04:06 +08:00
|
|
|
|
// 删除 CSP
|
|
|
|
|
res.Header.Del("Content-Security-Policy-Report-Only")
|
|
|
|
|
res.Header.Del("Report-To")
|
|
|
|
|
|
2023-05-06 11:08:57 +08:00
|
|
|
|
// 删除重定向前缀域名 cn.bing.com www.bing.com 等
|
|
|
|
|
location := res.Header.Get("Location")
|
|
|
|
|
if location != "" {
|
|
|
|
|
for _, delLocationDomain := range DEL_LOCATION_DOMAINS {
|
|
|
|
|
if strings.HasPrefix(location, delLocationDomain) {
|
|
|
|
|
res.Header.Set("Location", location[len(delLocationDomain):])
|
|
|
|
|
log.Println("Del Location Domain :", location)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-05 14:04:06 +08:00
|
|
|
|
|
2023-05-04 11:29:40 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
errorHandler := func(res http.ResponseWriter, req *http.Request, err error) {
|
|
|
|
|
log.Println("代理异常 :", err)
|
|
|
|
|
res.Write([]byte(err.Error()))
|
|
|
|
|
}
|
2023-05-05 10:05:45 +08:00
|
|
|
|
|
|
|
|
|
// tr := &http.Transport{
|
|
|
|
|
// TLSClientConfig: &tls.Config{
|
|
|
|
|
// // 如果只设置 InsecureSkipVerify: true对于这个问题不会有任何改变
|
|
|
|
|
// InsecureSkipVerify: true,
|
|
|
|
|
// ClientAuth: tls.NoClientCert,
|
|
|
|
|
// },
|
|
|
|
|
// }
|
|
|
|
|
|
2023-05-04 11:29:40 +08:00
|
|
|
|
// 代理请求 请求回来的内容 报错自动调用
|
|
|
|
|
return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func replaceResBody(originalBody string, originalScheme string, originalHost string) string {
|
|
|
|
|
modifiedBodyStr := originalBody
|
|
|
|
|
originalDomain := fmt.Sprintf("%s://%s", originalScheme, originalHost)
|
|
|
|
|
|
|
|
|
|
if strings.Contains(modifiedBodyStr, BING_URL.String()) {
|
|
|
|
|
modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_URL.String(), originalDomain)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 对话暂时支持国内网络,而且 Vercel 还不支持 Websocket ,先不用
|
|
|
|
|
// if strings.Contains(modifiedBodyStr, BING_CHAT_DOMAIN) {
|
|
|
|
|
// modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_CHAT_DOMAIN, originalDomain)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if strings.Contains(modifiedBodyStr, "https://www.bingapis.com") {
|
|
|
|
|
// modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, "https://www.bingapis.com", "https://bing.vcanbb.top")
|
|
|
|
|
// }
|
|
|
|
|
return modifiedBodyStr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func modifyGzipBody(res *http.Response, originalScheme string, originalHost string) error {
|
|
|
|
|
gz, err := gzip.NewReader(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer gz.Close()
|
|
|
|
|
|
|
|
|
|
bodyByte, err := io.ReadAll(gz)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
originalBody := string(bodyByte)
|
|
|
|
|
modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost)
|
|
|
|
|
// 修改响应内容
|
|
|
|
|
modifiedBody := []byte(modifiedBodyStr)
|
|
|
|
|
// gzip 压缩
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
writer := gzip.NewWriter(&buf)
|
|
|
|
|
defer writer.Close()
|
|
|
|
|
|
|
|
|
|
_, err = writer.Write(modifiedBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-05-05 20:12:23 +08:00
|
|
|
|
err = writer.Flush()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = writer.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-05-04 11:29:40 +08:00
|
|
|
|
|
|
|
|
|
// 修改 Content-Length 头
|
|
|
|
|
res.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
|
|
|
|
|
// 修改响应内容
|
2023-05-05 20:12:23 +08:00
|
|
|
|
res.Body = io.NopCloser(&buf)
|
2023-05-04 11:29:40 +08:00
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func modifyBrBody(res *http.Response, originalScheme string, originalHost string) error {
|
|
|
|
|
reader := brotli.NewReader(res.Body)
|
|
|
|
|
var uncompressed bytes.Buffer
|
|
|
|
|
uncompressed.ReadFrom(reader)
|
|
|
|
|
|
|
|
|
|
originalBody := uncompressed.String()
|
|
|
|
|
|
|
|
|
|
modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost)
|
|
|
|
|
|
|
|
|
|
// 修改响应内容
|
|
|
|
|
modifiedBody := []byte(modifiedBodyStr)
|
|
|
|
|
// br 压缩
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
writer := brotli.NewWriter(&buf)
|
|
|
|
|
writer.Write(modifiedBody)
|
|
|
|
|
writer.Close()
|
|
|
|
|
|
|
|
|
|
// 修改 Content-Length 头
|
|
|
|
|
// res.ContentLength = int64(buf.Len())
|
|
|
|
|
res.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
|
|
|
|
|
// 修改响应内容
|
|
|
|
|
res.Body = io.NopCloser(bytes.NewReader(buf.Bytes()))
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func modifyDefaultBody(res *http.Response, originalScheme string, originalHost string) error {
|
|
|
|
|
bodyByte, err := io.ReadAll(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
originalBody := string(bodyByte)
|
|
|
|
|
modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost)
|
|
|
|
|
// 修改响应内容
|
|
|
|
|
modifiedBody := []byte(modifiedBodyStr)
|
|
|
|
|
|
|
|
|
|
// 修改 Content-Length 头
|
|
|
|
|
// res.ContentLength = int64(buf.Len())
|
|
|
|
|
res.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody)))
|
|
|
|
|
// 修改响应内容
|
|
|
|
|
res.Body = io.NopCloser(bytes.NewReader(modifiedBody))
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|