186 lines
3.9 KiB
Go
186 lines
3.9 KiB
Go
package http
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"io"
|
|
"net"
|
|
"net/textproto"
|
|
"net/url"
|
|
"proxy-server/gateway/core"
|
|
"proxy-server/gateway/fwd/auth"
|
|
"strings"
|
|
|
|
"errors"
|
|
)
|
|
|
|
type Request struct {
|
|
conn net.Conn
|
|
reader *bufio.Reader
|
|
method string
|
|
uri string
|
|
proto string
|
|
headers *textproto.MIMEHeader
|
|
auth *core.AuthContext
|
|
dest *core.FwdAddr
|
|
}
|
|
|
|
func Process(ctx context.Context, conn net.Conn) (*core.Conn, error) {
|
|
reader := bufio.NewReader(conn)
|
|
textReader := textproto.NewReader(reader)
|
|
|
|
// 首行
|
|
line, err := textReader.ReadLine()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parts := strings.Fields(line)
|
|
if len(parts) != 3 {
|
|
return nil, errors.New("无效的 http 请求")
|
|
}
|
|
|
|
// 请求头
|
|
headers, err := textReader.ReadMIMEHeader()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析请求头失败: %v", err)
|
|
}
|
|
|
|
// 验证账号
|
|
authInfo := headers.Get("Proxy-Authorization")
|
|
var username *string = nil
|
|
var password *string = nil
|
|
if authInfo != "" {
|
|
authParts := strings.Split(authInfo, " ")
|
|
if len(authParts) != 2 {
|
|
return nil, errors.New("无效的 Proxy-Authorization")
|
|
}
|
|
if authParts[0] != "Basic" {
|
|
return nil, errors.New("不支持的认证方式")
|
|
}
|
|
authBytes, err := base64.URLEncoding.DecodeString(authParts[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解码认证信息失败: %v", err)
|
|
}
|
|
authPair := strings.Split(string(authBytes), ":")
|
|
username = &authPair[0]
|
|
password = &authPair[1]
|
|
}
|
|
|
|
authCtx, err := auth.Protect(conn, auth.Http, username, password)
|
|
if err != nil {
|
|
_, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("响应 407 失败: %v", err)
|
|
}
|
|
return nil, fmt.Errorf("验证账号失败: %v", err)
|
|
}
|
|
|
|
// 获取 Host
|
|
host := headers.Get("Host")
|
|
if host == "" {
|
|
return nil, errors.New("无效的 Host")
|
|
}
|
|
if !strings.Contains(host, ":") {
|
|
host += ":80"
|
|
}
|
|
addr, err := net.ResolveTCPAddr("tcp", host)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析 Host 失败: %v", err)
|
|
}
|
|
|
|
request := &Request{
|
|
conn: conn,
|
|
reader: reader,
|
|
method: parts[0],
|
|
uri: parts[1],
|
|
proto: parts[2],
|
|
headers: &headers,
|
|
dest: &core.FwdAddr{
|
|
IP: addr.IP,
|
|
Port: addr.Port,
|
|
Domain: host,
|
|
},
|
|
auth: authCtx,
|
|
}
|
|
|
|
var user *core.Conn
|
|
if parts[0] == "CONNECT" {
|
|
user, err = processHttps(ctx, request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
user, err = processHttp(ctx, request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func processHttps(ctx context.Context, req *Request) (*core.Conn, error) {
|
|
|
|
// 响应 CONNECT
|
|
_, err := req.conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("响应 CONNECT 失败: %v", err)
|
|
}
|
|
|
|
return &core.Conn{
|
|
Conn: req.conn,
|
|
Reader: req.reader,
|
|
Tag: uuid.New(),
|
|
Protocol: "http",
|
|
Dest: req.dest,
|
|
Auth: req.auth,
|
|
}, nil
|
|
}
|
|
|
|
func processHttp(ctx context.Context, req *Request) (*core.Conn, error) {
|
|
|
|
// 修改请求头
|
|
rawUrl, err := url.Parse(req.uri)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析请求地址失败: %v", err)
|
|
}
|
|
rawUrl.Scheme = ""
|
|
rawUrl.Host = ""
|
|
req.uri = rawUrl.String()
|
|
req.headers.Del("Proxy-Authorization")
|
|
|
|
// 构造请求
|
|
sb := strings.Builder{}
|
|
|
|
sb.WriteString(req.method)
|
|
sb.WriteString(" ")
|
|
sb.WriteString(req.uri)
|
|
sb.WriteString(" ")
|
|
sb.WriteString(req.proto)
|
|
sb.WriteString("\r\n")
|
|
|
|
for k, v := range *req.headers {
|
|
sb.WriteString(k)
|
|
sb.WriteString(": ")
|
|
sb.WriteString(strings.Join(v, ","))
|
|
sb.WriteString("\r\n")
|
|
}
|
|
|
|
sb.WriteString("\r\n")
|
|
|
|
mReader := io.MultiReader(strings.NewReader(sb.String()), req.conn)
|
|
newReader := bufio.NewReader(mReader)
|
|
|
|
return &core.Conn{
|
|
Conn: req.conn,
|
|
Reader: newReader,
|
|
Tag: uuid.New(),
|
|
Protocol: "http",
|
|
Dest: req.dest,
|
|
Auth: req.auth,
|
|
}, nil
|
|
}
|