mesh/merge-gate head a01e1ea7fb64 merged into trunk d74c1cb995a4 — pass: builds netcheck → ace, g14, novox, shanks; no bus step; every machine composes wit…
mesh/repo-check head a01e1ea7fb64 merged into trunk d74c1cb995a4 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
213 lines
6.5 KiB
Go
213 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The httptest servers listen on loopback, which the tool refuses; the tests allow it, and the refusal
|
|
// tests put the refusal back.
|
|
func TestMain(m *testing.M) {
|
|
allowLoopback = true
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func refusing(t *testing.T) {
|
|
t.Helper()
|
|
allowLoopback = false
|
|
t.Cleanup(func() { allowLoopback = true })
|
|
}
|
|
|
|
func TestRequestRefusesLocalAddresses(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t.Error("the server was reached")
|
|
}))
|
|
defer srv.Close()
|
|
port := srv.Listener.Addr().(*net.TCPAddr).Port
|
|
refusing(t)
|
|
for _, host := range []string{
|
|
"127.0.0.1", "localhost", "127.1", "2130706433", "0x7f.0.0.1", "0x7f.1", "0177.0.0.1", "[::1]", "[::ffff:127.0.0.1]", "0.0.0.0", "[::]",
|
|
"169.254.169.254", "[fe80::1]",
|
|
} {
|
|
u := "http://" + host + ":" + strconv.Itoa(port) + "/"
|
|
_, err := asked(t, map[string]any{"url": u, "timeout_ms": float64(2000)})
|
|
if err == nil || !strings.Contains(err.Error(), "refus") {
|
|
t.Errorf("%s: want a refusal, got %v", u, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestShortFormAddressIsRefusedOnItsOwnWords(t *testing.T) {
|
|
for _, host := range []string{"127.1", "2130706433", "0x7f.0.0.1", "0x7f.1", "0177.0.0.1"} {
|
|
_, err := asked(t, map[string]any{"url": "http://" + host + ":1/", "timeout_ms": float64(2000)})
|
|
if err == nil || !strings.Contains(err.Error(), "short or legacy form") {
|
|
t.Errorf("%s: want the short-form refusal, got %v", host, err)
|
|
}
|
|
}
|
|
for _, host := range []string{"example.com", "127.0.0.1", "1.example.com", "[::1]", "10.0.0.1"} {
|
|
if shortFormAddress(strings.Trim(host, "[]")) {
|
|
t.Errorf("%s taken for a short form", host)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRequestIgnoresProxyEnvironment(t *testing.T) {
|
|
t.Setenv("HTTP_PROXY", "http://127.0.0.1:1")
|
|
t.Setenv("http_proxy", "http://127.0.0.1:1")
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
defer srv.Close()
|
|
if _, err := asked(t, map[string]any{"url": "http://example.invalid:80/"}); err == nil || strings.Contains(err.Error(), "127.0.0.1:1") {
|
|
t.Fatalf("a proxy seems to have been used: %v", err)
|
|
}
|
|
}
|
|
|
|
func asked(t *testing.T, args map[string]any) (RequestResult, error) {
|
|
t.Helper()
|
|
got, err := runRequest(args)
|
|
if err != nil {
|
|
return RequestResult{}, err
|
|
}
|
|
return got.(RequestResult), nil
|
|
}
|
|
|
|
func TestRequestGet(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("X-Seen", r.Method+" "+r.URL.RequestURI())
|
|
_, _ = io.WriteString(w, "hello")
|
|
}))
|
|
defer srv.Close()
|
|
res, err := asked(t, map[string]any{"url": srv.URL + "/mail/config-v1.1.xml?a=1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Status != 200 || res.StatusLine != "200 OK" || res.Body != "hello" || res.Cut || res.Encoding != "text" {
|
|
t.Fatalf("unexpected answer %+v", res)
|
|
}
|
|
if res.Headers["X-Seen"][0] != "GET /mail/config-v1.1.xml?a=1" {
|
|
t.Fatalf("headers %+v", res.Headers)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeadHasNoBody(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("X-Method", r.Method)
|
|
}))
|
|
defer srv.Close()
|
|
res, err := asked(t, map[string]any{"url": srv.URL, "method": "HEAD"})
|
|
if err != nil || res.Headers["X-Method"][0] != "HEAD" || res.Body != "" {
|
|
t.Fatalf("%+v %v", res, err)
|
|
}
|
|
}
|
|
|
|
func TestRequestPostWithBodyAndHeaders(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
b, _ := io.ReadAll(r.Body)
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = io.WriteString(w, r.Method+"|"+r.Header.Get("Content-Type")+"|"+string(b))
|
|
}))
|
|
defer srv.Close()
|
|
res, err := asked(t, map[string]any{
|
|
"url": srv.URL, "method": "POST", "body": "<a/>",
|
|
"headers": map[string]any{"Content-Type": "text/xml"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Status != 201 || res.Body != "POST|text/xml|<a/>" {
|
|
t.Fatalf("%+v", res)
|
|
}
|
|
}
|
|
|
|
func TestRequestBodyTooLarge(t *testing.T) {
|
|
_, err := asked(t, map[string]any{"url": "http://127.0.0.1:1", "method": "POST", "body": strings.Repeat("x", MostRequestBody+1)})
|
|
if err == nil || !strings.Contains(err.Error(), "body") {
|
|
t.Fatalf("want a body error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRequestCutBody(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = io.WriteString(w, strings.Repeat("a", MostResponseBody+10))
|
|
}))
|
|
defer srv.Close()
|
|
res, err := asked(t, map[string]any{"url": srv.URL})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !res.Cut || len(res.Body) != MostResponseBody {
|
|
t.Fatalf("cut=%v len=%d", res.Cut, len(res.Body))
|
|
}
|
|
}
|
|
|
|
func TestRequestBinaryBodyIsBase64(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte{0xff, 0xfe, 0x00, 0x01})
|
|
}))
|
|
defer srv.Close()
|
|
res, err := asked(t, map[string]any{"url": srv.URL})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, derr := base64.StdEncoding.DecodeString(res.Body)
|
|
if res.Encoding != "base64" || derr != nil || len(raw) != 4 {
|
|
t.Fatalf("%+v", res)
|
|
}
|
|
}
|
|
|
|
func TestRequestRedirectReportedNotFollowed(t *testing.T) {
|
|
hit := false
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/there" {
|
|
hit = true
|
|
}
|
|
http.Redirect(w, r, "/there", http.StatusFound)
|
|
}))
|
|
defer srv.Close()
|
|
res, err := asked(t, map[string]any{"url": srv.URL})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Status != 302 || res.Redirect != "/there" || hit {
|
|
t.Fatalf("%+v hit=%v", res, hit)
|
|
}
|
|
}
|
|
|
|
func TestRequestRefusesOtherSchemes(t *testing.T) {
|
|
for _, u := range []string{"file:///etc/passwd", "ftp://example.org/x", "gopher://x", "//x/y", "example.org"} {
|
|
if _, err := asked(t, map[string]any{"url": u}); err == nil {
|
|
t.Errorf("%q was not refused", u)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRequestRefusesOtherMethods(t *testing.T) {
|
|
if _, err := asked(t, map[string]any{"url": "http://127.0.0.1:1", "method": "DELETE"}); err == nil {
|
|
t.Fatal("DELETE was not refused")
|
|
}
|
|
}
|
|
|
|
func TestRequestTimeout(t *testing.T) {
|
|
release := make(chan struct{})
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
select {
|
|
case <-release:
|
|
case <-r.Context().Done():
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
defer close(release)
|
|
start := time.Now()
|
|
_, err := asked(t, map[string]any{"url": srv.URL, "timeout_ms": float64(100)})
|
|
if err == nil || time.Since(start) > 3*time.Second {
|
|
t.Fatalf("want a timeout error quickly, got %v after %v", err, time.Since(start))
|
|
}
|
|
}
|