mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-08 16:02:24 +00:00
feat: decide the hosted form URL scheme from the host rather than the port, so an install that terminates TLS on a non-default port (forms.example.com:8443) keeps https in its share links, embeds and base_url instead of being downgraded to http by the port check, with a private-network host now treated as the LAN install it is, one scheme helper shared by FormsBaseURL and FormURLOn so the builder's base_url and a form's share_url can never disagree, and a table test covering every install shape
This commit is contained in:
@@ -63,16 +63,26 @@ func GetInviteURL(token string) string {
|
||||
// pages do not live on the API origin, so there is nothing to fall back to,
|
||||
// and a share link pointing at the wrong process is worse than none.
|
||||
func FormsBaseURL() string {
|
||||
if host := strings.TrimSpace(os.Getenv("FORMS_DOMAIN")); host != "" {
|
||||
host = strings.TrimPrefix(strings.TrimPrefix(host, "https://"), "http://")
|
||||
host = strings.TrimRight(host, "/")
|
||||
scheme := "https"
|
||||
if strings.HasPrefix(host, "localhost") || strings.HasPrefix(host, "127.0.0.1") {
|
||||
scheme = "http"
|
||||
}
|
||||
return scheme + "://" + host
|
||||
host := NormalizeTrackingHost(os.Getenv("FORMS_DOMAIN"))
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
return formsScheme(host) + "://" + host
|
||||
}
|
||||
|
||||
// formsScheme is https except where TLS cannot be terminated: a form page on a
|
||||
// loopback or private-network host is a development or LAN install. The port is
|
||||
// deliberately not a signal, because an install can terminate TLS on any port
|
||||
// and inferring http from one handed an https deployment http:// share links.
|
||||
func formsScheme(host string) string {
|
||||
name := hostWithoutPort(NormalizeTrackingHost(host))
|
||||
if name == "localhost" || strings.HasSuffix(name, ".localhost") {
|
||||
return "http"
|
||||
}
|
||||
if ip := net.ParseIP(strings.Trim(name, "[]")); ip != nil && (ip.IsLoopback() || ip.IsPrivate()) {
|
||||
return "http"
|
||||
}
|
||||
return "https"
|
||||
}
|
||||
|
||||
// FormsHostname is the bare host this install serves forms on. It is the
|
||||
@@ -97,18 +107,7 @@ func FormURLOn(host, publicID string) string {
|
||||
if host == "" {
|
||||
return GetFormURL(publicID)
|
||||
}
|
||||
scheme := "https"
|
||||
if name, port, err := net.SplitHostPort(host); err == nil {
|
||||
if port != "" && port != "443" {
|
||||
scheme = "http"
|
||||
}
|
||||
if name == "localhost" || strings.HasSuffix(name, ".localhost") {
|
||||
scheme = "http"
|
||||
}
|
||||
} else if host == "localhost" || strings.HasSuffix(host, ".localhost") {
|
||||
scheme = "http"
|
||||
}
|
||||
return scheme + "://" + host + "/f/" + url.PathEscape(publicID)
|
||||
return formsScheme(host) + "://" + host + "/f/" + url.PathEscape(publicID)
|
||||
}
|
||||
|
||||
// GetFormURL is the hosted page for one form; empty when no base is known.
|
||||
|
||||
@@ -2,22 +2,56 @@ package config
|
||||
|
||||
import "testing"
|
||||
|
||||
// Clients dial whatever GET /v1/auth/config advertises, so every form an
|
||||
// operator plausibly writes has to normalise to the Phoenix transport path.
|
||||
func TestWebsocketURLNormalisation(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"wss://ws.example.com": "wss://ws.example.com/socket/websocket",
|
||||
"wss://ws.example.com/": "wss://ws.example.com/socket/websocket",
|
||||
"wss://ws.example.com/socket": "wss://ws.example.com/socket/websocket",
|
||||
"wss://ws.example.com/socket/": "wss://ws.example.com/socket/websocket",
|
||||
"wss://ws.example.com/socket/websocket": "wss://ws.example.com/socket/websocket",
|
||||
"ws://localhost:4000/socket/websocket": "ws://localhost:4000/socket/websocket",
|
||||
"": "",
|
||||
// The hosted form URL has to be reachable on every install shape: the shared
|
||||
// host keeps its port (a share link that drops it points at nothing), and the
|
||||
// scheme follows the host rather than the port, because an install can
|
||||
// terminate TLS on any port and a ported https deployment must not be handed
|
||||
// http:// links (PR #368).
|
||||
func TestFormURLsFollowTheInstallHost(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
formsDomain string
|
||||
wantBase string
|
||||
wantShare string
|
||||
wantCNAME string
|
||||
}{
|
||||
{"localhost:8090", "http://localhost:8090", "http://localhost:8090/f/abc", "localhost"},
|
||||
{"127.0.0.1:8090", "http://127.0.0.1:8090", "http://127.0.0.1:8090/f/abc", "127.0.0.1"},
|
||||
{"192.168.1.5:8090", "http://192.168.1.5:8090", "http://192.168.1.5:8090/f/abc", "192.168.1.5"},
|
||||
{"forms.example.com", "https://forms.example.com", "https://forms.example.com/f/abc", "forms.example.com"},
|
||||
{"forms.example.com:8443", "https://forms.example.com:8443", "https://forms.example.com:8443/f/abc", "forms.example.com"},
|
||||
{"https://Forms.Example.com/", "https://forms.example.com", "https://forms.example.com/f/abc", "forms.example.com"},
|
||||
} {
|
||||
t.Run(tc.formsDomain, func(t *testing.T) {
|
||||
t.Setenv("FORMS_DOMAIN", tc.formsDomain)
|
||||
if got := FormsBaseURL(); got != tc.wantBase {
|
||||
t.Errorf("FormsBaseURL() = %q, want %q", got, tc.wantBase)
|
||||
}
|
||||
if got := GetFormURL("abc"); got != tc.wantShare {
|
||||
t.Errorf("GetFormURL() = %q, want %q", got, tc.wantShare)
|
||||
}
|
||||
// What the handler stamps on every form: the shared host, resolved
|
||||
// through FormsHost, then built into a URL.
|
||||
if got := FormURLOn(FormsURLHost(), "abc"); got != tc.wantShare {
|
||||
t.Errorf("FormURLOn(FormsURLHost()) = %q, want %q", got, tc.wantShare)
|
||||
}
|
||||
// The CNAME target is a DNS name, so it never carries the port.
|
||||
if got := FormsHostname(); got != tc.wantCNAME {
|
||||
t.Errorf("FormsHostname() = %q, want %q", got, tc.wantCNAME)
|
||||
}
|
||||
})
|
||||
}
|
||||
for in, want := range cases {
|
||||
t.Setenv("WEBSOCKET_URL", in)
|
||||
if got := WebsocketURL(); got != want {
|
||||
t.Errorf("WebsocketURL(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A verified custom forms domain replaces the shared host and is always a bare
|
||||
// name, so its links stay https whatever the install runs on.
|
||||
func TestFormURLOnCustomDomain(t *testing.T) {
|
||||
t.Setenv("FORMS_DOMAIN", "localhost:8090")
|
||||
if got := FormURLOn("forms.acme.com", "abc"); got != "https://forms.acme.com/f/abc" {
|
||||
t.Errorf("custom domain URL = %q", got)
|
||||
}
|
||||
// No host and no configured base is an empty URL, never a relative one.
|
||||
t.Setenv("FORMS_DOMAIN", "")
|
||||
if got := FormURLOn("", "abc"); got != "" {
|
||||
t.Errorf("unconfigured install URL = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user