guard_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package fetch
  2. import (
  3. "context"
  4. "errors"
  5. "net/http"
  6. "net/netip"
  7. "strings"
  8. "sync/atomic"
  9. "testing"
  10. )
  11. func TestIsPublic(t *testing.T) {
  12. tests := map[string]bool{
  13. "93.184.215.14": true,
  14. "2606:4700:4700::1111": true,
  15. "127.0.0.1": false, // loopback
  16. "::1": false,
  17. "10.1.2.3": false, // private networks
  18. "172.16.0.1": false,
  19. "192.168.1.1": false,
  20. "fd12:3456::1": false,
  21. "169.254.169.254": false, // link-local: cloud metadata services
  22. "fe80::1": false,
  23. "100.100.1.1": false, // carrier-grade NAT, Tailscale
  24. "0.0.0.0": false,
  25. "::": false,
  26. "224.0.0.1": false, // multicast
  27. "255.255.255.255": false,
  28. "::ffff:192.168.1.1": false, // IPv4-mapped
  29. }
  30. for addr, want := range tests {
  31. if got := isPublic(netip.MustParseAddr(addr)); got != want {
  32. t.Errorf("isPublic(%s) = %v, want %v", addr, got, want)
  33. }
  34. }
  35. }
  36. func TestIsLocal(t *testing.T) {
  37. ctx := context.Background()
  38. for _, host := range []string{"localhost", "127.0.0.1", "::1", "192.168.1.20", "169.254.169.254"} {
  39. if !IsLocal(ctx, host) {
  40. t.Errorf("IsLocal(%q) = false, want true", host)
  41. }
  42. }
  43. for _, host := range []string{"93.184.215.14", "2606:4700:4700::1111", "name.invalid"} {
  44. if IsLocal(ctx, host) {
  45. t.Errorf("IsLocal(%q) = true, want false", host)
  46. }
  47. }
  48. }
  49. func TestGuardBlocksLocalPage(t *testing.T) {
  50. var hits atomic.Int32
  51. srv := serve(t, func(w http.ResponseWriter, r *http.Request) { hits.Add(1) })
  52. f := New(false)
  53. f.Retries = 0
  54. _, err := f.Fetch(context.Background(), mustParse(t, srv.URL+"/page"))
  55. var blocked *BlockedError
  56. if !errors.As(err, &blocked) {
  57. t.Fatalf("error = %v, want a BlockedError", err)
  58. }
  59. if !strings.Contains(err.Error(), "is a local or private address (127.0.0.1)") {
  60. t.Errorf("error = %q, want an explanation", err)
  61. }
  62. if hits.Load() != 0 {
  63. t.Errorf("server received %d requests, want none", hits.Load())
  64. }
  65. }
  66. func TestGuardBlocksRedirectToLocal(t *testing.T) {
  67. var localHits atomic.Int32
  68. local := serve(t, func(w http.ResponseWriter, r *http.Request) { localHits.Add(1) })
  69. public := serve(t, func(w http.ResponseWriter, r *http.Request) {
  70. http.Redirect(w, r, local.URL+"/admin", http.StatusFound)
  71. })
  72. // Treat the "public" server as being on the internet.
  73. publicAddr := netip.MustParseAddrPort(strings.TrimPrefix(public.URL, "http://"))
  74. defer func(old func(netip.AddrPort) bool) { permitted = old }(permitted)
  75. permitted = func(ap netip.AddrPort) bool { return ap == publicAddr }
  76. f := New(false)
  77. f.Retries = 0
  78. _, err := f.Fetch(context.Background(), mustParse(t, public.URL+"/story"))
  79. var blocked *BlockedError
  80. if !errors.As(err, &blocked) {
  81. t.Fatalf("error = %v, want a BlockedError", err)
  82. }
  83. if localHits.Load() != 0 {
  84. t.Errorf("local server received %d requests, want none", localHits.Load())
  85. }
  86. }
  87. func TestFetchRefusesFileRedirect(t *testing.T) {
  88. srv := serve(t, func(w http.ResponseWriter, r *http.Request) {
  89. http.Redirect(w, r, "file:///etc/hosts", http.StatusFound)
  90. })
  91. f := testFetcher()
  92. f.Retries = 0
  93. page, err := f.Fetch(context.Background(), mustParse(t, srv.URL+"/page"))
  94. if err == nil || page != nil {
  95. t.Fatalf("Fetch followed a redirect to a local file: page = %+v, err = %v", page, err)
  96. }
  97. if !strings.Contains(err.Error(), "unsupported protocol scheme") {
  98. t.Errorf("error = %v", err)
  99. }
  100. }
  101. func TestProxyAddrs(t *testing.T) {
  102. // Set both spellings: which one wins depends on the x/net version.
  103. for _, name := range []string{"HTTP_PROXY", "http_proxy"} {
  104. t.Setenv(name, "http://10.0.0.2")
  105. }
  106. for _, name := range []string{"HTTPS_PROXY", "https_proxy"} {
  107. t.Setenv(name, "proxy.lan:3128")
  108. }
  109. got := proxyAddrs()
  110. for _, want := range []string{"10.0.0.2:80", "proxy.lan:3128"} {
  111. if !got[want] {
  112. t.Errorf("proxyAddrs() = %v, missing %s", got, want)
  113. }
  114. }
  115. }