mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-14 09:06:37 +08:00
141 lines
4.2 KiB
Go
141 lines
4.2 KiB
Go
package main
|
||
|
||
import (
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestSplitListenerRoutesByFirstByte(t *testing.T) {
|
||
raw, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
split := newSplitListener(raw)
|
||
addr := raw.Addr().String()
|
||
|
||
// TLS:首字节 0x16。分流器偷看过的字节必须还能被上层读到,否则握手会失败。
|
||
handshake := []byte{0x16, 0x03, 0x01, 0x00, 0x2c}
|
||
tlsClient, err := net.Dial("tcp", addr)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer func() { _ = tlsClient.Close() }()
|
||
if _, err := tlsClient.Write(handshake); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
tlsConn := <-split.tlsCh
|
||
defer func() { _ = tlsConn.Close() }()
|
||
got := make([]byte, len(handshake))
|
||
if _, err := io.ReadFull(tlsConn, got); err != nil {
|
||
t.Fatalf("read peeked tls bytes: %v", err)
|
||
}
|
||
if string(got) != string(handshake) {
|
||
t.Fatalf("tls bytes = %v, want %v", got, handshake)
|
||
}
|
||
|
||
// 明文 HTTP:请求行走另一路,同样要读到偷看过的首字节。
|
||
request := "GET /api/health HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
||
plainClient, err := net.Dial("tcp", addr)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer func() { _ = plainClient.Close() }()
|
||
if _, err := plainClient.Write([]byte(request)); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
plainConn := <-split.plainCh
|
||
defer func() { _ = plainConn.Close() }()
|
||
head := make([]byte, len("GET /"))
|
||
if _, err := io.ReadFull(plainConn, head); err != nil {
|
||
t.Fatalf("read peeked plain bytes: %v", err)
|
||
}
|
||
if string(head) != request[:len(head)] {
|
||
t.Fatalf("plain bytes = %q, want prefix of %q", head, request)
|
||
}
|
||
|
||
// 底层监听关闭后两路 Accept 都要返回 net.ErrClosed,http.Server 才能收尾。
|
||
_ = raw.Close()
|
||
for _, ln := range []net.Listener{split.tlsListener(), split.plainListener()} {
|
||
deadline := time.Now().Add(3 * time.Second)
|
||
for {
|
||
_, err := ln.Accept()
|
||
if err == net.ErrClosed {
|
||
break
|
||
}
|
||
if time.Now().After(deadline) {
|
||
t.Fatalf("Accept after close = %v, want net.ErrClosed", err)
|
||
}
|
||
time.Sleep(10 * time.Millisecond)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 只连不发的半开连接不应被分给任何一路,而应在偷看超时后被丢弃。
|
||
func TestSplitListenerDropsIdleConnection(t *testing.T) {
|
||
raw, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer func() { _ = raw.Close() }()
|
||
split := newSplitListenerWithTimeout(raw, 200*time.Millisecond)
|
||
|
||
conn, err := net.Dial("tcp", raw.Addr().String())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer func() { _ = conn.Close() }()
|
||
|
||
select {
|
||
case c := <-split.plainCh:
|
||
_ = c.Close()
|
||
t.Fatal("idle connection should not be dispatched")
|
||
case c := <-split.tlsCh:
|
||
_ = c.Close()
|
||
t.Fatal("idle connection should not be dispatched")
|
||
case <-time.After(time.Second):
|
||
}
|
||
}
|
||
|
||
func TestHTTPSRedirectTarget(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
host string
|
||
rawURL string
|
||
want string
|
||
wantErr bool
|
||
}{
|
||
{name: "keeps host port path and query", host: "10.0.0.2:8080", rawURL: "/library?tab=1&q=a%20b", want: "https://10.0.0.2:8080/library?tab=1&q=a%20b"},
|
||
{name: "root path", host: "nas.local:18080", rawURL: "/", want: "https://nas.local:18080/"},
|
||
{name: "keeps escaping of user input", host: "10.0.0.2", rawURL: "/a%2Fb", want: "https://10.0.0.2/a%2Fb"},
|
||
{name: "absolute form request line", host: "10.0.0.2:8080", rawURL: "http://10.0.0.2:8080/api/x?y=1", want: "https://10.0.0.2:8080/api/x?y=1"},
|
||
{name: "rejects empty host", host: "", rawURL: "/", wantErr: true},
|
||
{name: "rejects host with slash", host: "evil.com/path", rawURL: "/", wantErr: true},
|
||
{name: "rejects userinfo host", host: "user@evil.com", rawURL: "/", wantErr: true},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
req, err := http.NewRequest(http.MethodGet, tc.rawURL, nil)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
req.Host = tc.host
|
||
|
||
if host := redirectHostHeader(req); host == "" {
|
||
if !tc.wantErr {
|
||
t.Fatalf("redirectHostHeader(%q) rejected, want accepted", tc.host)
|
||
}
|
||
return
|
||
}
|
||
if tc.wantErr {
|
||
t.Fatalf("redirectHostHeader(%q) accepted, want rejected", tc.host)
|
||
}
|
||
if got := httpsRedirectURL(tc.host, req); got != tc.want {
|
||
t.Fatalf("redirect target = %q, want %q", got, tc.want)
|
||
}
|
||
})
|
||
}
|
||
}
|