s-ui/network/auto_https_conn_test.go
2026-09-12 18:49:52 +02:00

107 lines
2.9 KiB
Go

package network
import (
"bufio"
"io"
"net"
"net/http"
"testing"
"time"
)
// A plaintext request arriving on the TLS port must be answered with a redirect
// the client can actually read. The response used to be a zero-valued
// http.Response, which serialises as "HTTP/0.0 307" -- curl rejects that with
// "unsupported HTTP version", so the redirect never worked at all.
func TestPlainRequestGetsAReadableRedirect(t *testing.T) {
client, server := net.Pipe()
conn := NewAutoHttpsConn(server)
go func() {
_, _ = io.WriteString(client, "GET /app/ HTTP/1.1\r\nHost: panel.example:2095\r\n\r\n")
}()
done := make(chan *http.Response, 1)
errc := make(chan error, 1)
go func() {
resp, err := http.ReadResponse(bufio.NewReader(client), nil)
if err != nil {
errc <- err
return
}
done <- resp
}()
// Drives readRequest, which writes the redirect on the other side.
buf := make([]byte, 1)
_, _ = conn.Read(buf)
select {
case err := <-errc:
t.Fatalf("the client could not read the response: %v", err)
case resp := <-done:
defer resp.Body.Close()
if resp.StatusCode != http.StatusTemporaryRedirect {
t.Errorf("status = %d, want 307", resp.StatusCode)
}
if resp.ProtoMajor != 1 || resp.ProtoMinor != 1 {
t.Errorf("proto = HTTP/%d.%d, want HTTP/1.1", resp.ProtoMajor, resp.ProtoMinor)
}
if got, want := resp.Header.Get("Location"), "https://panel.example:2095/app/"; got != want {
t.Errorf("Location = %q, want %q", got, want)
}
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for the redirect")
}
}
// A TLS handshake is not an HTTP request: the bytes have to be handed straight
// through to the TLS listener, not consumed.
func TestTLSBytesArePassedThrough(t *testing.T) {
client, server := net.Pipe()
conn := NewAutoHttpsConn(server)
// The opening bytes of a TLS ClientHello.
hello := []byte{0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x01, 0x00}
go func() {
_, _ = client.Write(hello)
}()
got := make([]byte, 0, len(hello))
buf := make([]byte, len(hello))
deadline := time.Now().Add(5 * time.Second)
for len(got) < len(hello) && time.Now().Before(deadline) {
n, err := conn.Read(buf)
got = append(got, buf[:n]...)
if err != nil {
break
}
}
if string(got) != string(hello) {
t.Errorf("read %v, want the handshake bytes %v", got, hello)
}
}
// A peer that connects and sends nothing used to pin a goroutine and a file
// descriptor for the life of the process.
func TestSilentPeerDoesNotBlockForever(t *testing.T) {
if testing.Short() {
t.Skip("waits on the first-read deadline")
}
client, server := net.Pipe()
defer client.Close()
conn := NewAutoHttpsConn(server)
done := make(chan struct{})
go func() {
buf := make([]byte, 1)
_, _ = conn.Read(buf)
close(done)
}()
select {
case <-done:
case <-time.After(firstReadTimeout + 10*time.Second):
t.Fatalf("the read did not give up within %v", firstReadTimeout)
}
}