1// SPDX-License-Identifier: MIT
2
3package sandbox
4
5import (
6 "context"
7 "errors"
8 "strings"
9 "testing"
10)
11
12func TestParseGuestForge(t *testing.T) {
13 ok := map[string]struct {
14 insecure bool
15 host, origin string
16 port uint16
17 }{
18 "https://ermite.example.org": {false, "ermite.example.org", "https://ermite.example.org", 443},
19 "https://Ermite.Example.org:8443": {false, "ermite.example.org", "https://ermite.example.org:8443", 8443},
20 "https://ermite.example.org/": {false, "ermite.example.org", "https://ermite.example.org", 443},
21 "https://ermite.example.org.": {false, "ermite.example.org", "https://ermite.example.org", 443},
22 "http://ermite.bnema.dev:8088": {true, "ermite.bnema.dev", "http://ermite.bnema.dev:8088", 8088},
23 "http://forge": {true, "forge", "http://forge", 80},
24 "https://a-b.c1.test:65535": {false, "a-b.c1.test", "https://a-b.c1.test:65535", 65535},
25 }
26 for raw, w := range ok {
27 g, err := ParseGuestForge(raw, w.insecure)
28 if err != nil {
29 t.Errorf("%s: %v", raw, err)
30 continue
31 }
32 if g.Host != w.host || g.Port != w.port || g.Origin != w.origin {
33 t.Errorf("%s: %+v", raw, g)
34 }
35 }
36 bad := map[string]string{
37 "http://ermite.bnema.dev:8088": "INSECURE_HTTP", // plain http needs the opt-in
38 "https://10.0.0.1": "IP address",
39 "https://10.0.0.1:8443": "IP address",
40 "https://[::1]": "IP address",
41 "https://[2001:db8::1]:8443": "IP address",
42 "https://2130706433": "IP address",
43 "https://127.1": "IP address",
44 "https://*.example.org": "valid host name",
45 "https://exa_mple.org": "valid host name",
46 "https://-a.example.org": "valid host name",
47 "https://a..example.org": "valid host name",
48 "https://bücher.example": "valid host name",
49 "https://" + strings.Repeat("a", 64) + ".test": "valid host name",
50 "https://ermite.example.org:0": "1-65535",
51 "https://ermite.example.org:65536": "",
52 "https://ermite.example.org:99999": "",
53 "https://ermite.example.org/sub": "bare origin",
54 "https://ermite.example.org/?q=1": "bare origin",
55 "https://u:p@ermite.example.org": "bare origin",
56 "ftp://ermite.example.org": "http or https",
57 "ermite.example.org": "valid URL",
58 "": "valid URL",
59 }
60 for raw, frag := range bad {
61 if _, err := ParseGuestForge(raw, false); err == nil || !strings.Contains(err.Error(), frag) || !strings.Contains(err.Error(), EnvGuestForge) {
62 t.Errorf("%q: %v (want an error with %q)", raw, err, frag)
63 }
64 }
65 // With the opt-in for plain http, the other refusals still apply.
66 for _, raw := range []string{"http://10.0.0.1:8088", "http://localhost:99999", "http://[::1]:8088"} {
67 if _, err := ParseGuestForge(raw, true); err == nil {
68 t.Errorf("%q accepted", raw)
69 }
70 }
71}
72
73func TestResolveGuestForge(t *testing.T) {
74 ctx := context.Background()
75 var asked string
76 lookup := func(_ context.Context, h string) ([]string, error) { asked = h; return []string{"127.0.0.1"}, nil }
77 g, err := ResolveGuestForge(ctx, "http://ermite.bnema.dev:8088", "", true, lookup)
78 if err != nil || g.Port != 8088 || asked != "ermite.bnema.dev" {
79 t.Fatalf("%+v %v %q", g, err, asked)
80 }
81 _, err = ResolveGuestForge(ctx, "https://nx.example.org", "", false, func(context.Context, string) ([]string, error) {
82 return nil, errors.New("no such host")
83 })
84 if err == nil || !strings.Contains(err.Error(), "does not resolve") {
85 t.Fatalf("%v", err)
86 }
87 _, err = ResolveGuestForge(ctx, "https://empty.example.org", "", false, func(context.Context, string) ([]string, error) { return nil, nil })
88 if err == nil || !strings.Contains(err.Error(), "no address") {
89 t.Fatalf("%v", err)
90 }
91 called := false
92 _, err = ResolveGuestForge(ctx, "https://10.0.0.1", "", false, func(context.Context, string) ([]string, error) { called = true; return nil, nil })
93 if err == nil || called {
94 t.Fatalf("an IP literal was looked up or accepted: %v", err)
95 }
96}
97
98func TestGuestForgeNilIsOff(t *testing.T) {
99 var g *GuestForge
100 if g.DNSName() != "" || g.TCPPort() != 0 {
101 t.Fatal("nil must mean off")
102 }
103 g, _ = ParseGuestForge("https://ermite.example.org:8443", false)
104 if g.Warning() != "guest forge access: sandboxes may reach https://ermite.example.org:8443 on the host" {
105 t.Fatal(g.Warning())
106 }
107}
108
109func TestResolveGuestForgeTarget(t *testing.T) {
110 ctx := context.Background()
111 var asked string
112 lookup := func(_ context.Context, h string) ([]string, error) { asked = h; return []string{"10.89.0.5"}, nil }
113 g, err := ResolveGuestForge(ctx, "http://ermite:7341", "ermite:7341", true, lookup)
114 if err != nil || g.Target != "ermite:7341" || g.RelayTarget() != "ermite:7341" || asked != "ermite" || g.Port != 7341 {
115 t.Fatalf("%+v %v %q", g, err, asked)
116 }
117 asked = ""
118 if g, err = ResolveGuestForge(ctx, "https://git.example.org", "10.89.0.5:7341", false, lookup); err != nil || asked != "" || g.Target != "10.89.0.5:7341" {
119 t.Fatalf("an IP target must not be looked up: %+v %v %q", g, err, asked)
120 }
121 for _, bad := range []string{"ermite", "ermite:0", "ermite:70000", ":7341", "bad_host:1", "*.x:1"} {
122 if _, err := ResolveGuestForge(ctx, "http://ermite:7341", bad, true, lookup); err == nil || !strings.Contains(err.Error(), EnvGuestForgeTarget) {
123 t.Errorf("%q: %v", bad, err)
124 }
125 }
126 if (*GuestForge)(nil).RelayTarget() != "" {
127 t.Fatal("nil RelayTarget")
128 }
129}
130
131func TestLoadConfigGuestForge(t *testing.T) {
132 get := func(v string) func(string) string {
133 return func(k string) string {
134 if k == EnvGuestForge {
135 return v
136 }
137 return ""
138 }
139 }
140 for v, want := range map[string]bool{"": false, "0": false, "no": false, "1": true, " true ": true, "YES": true} {
141 c, err := LoadConfig(get(v))
142 if err != nil || c.GuestForgeEnabled != want {
143 t.Errorf("%q: %v %v", v, c.GuestForgeEnabled, err)
144 }
145 }
146 if _, err := LoadConfig(get("on")); err == nil || !strings.Contains(err.Error(), EnvGuestForge) {
147 t.Errorf("a bad value was accepted: %v", err)
148 }
149}