@@ -3,6 +3,7 @@ package appurl
33import (
44"fmt"
55"net"
6+ "net/url"
67"regexp"
78"strings"
89
2021validHostnameLabelRegex = regexp .MustCompile (`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` )
2122)
2223
24+ // SubdomainAppHost returns the URL of the apphost for subdomain based apps.
25+ // It will omit the scheme.
26+ //
27+ // Arguments:
28+ // apphost: Expected to contain a wildcard, example: "*.coder.com"
29+ // accessURL: The access url for the deployment.
30+ //
31+ // Returns:
32+ // 'apphost:port'
33+ //
34+ // For backwards compatibility and for "accessurl=localhost:0" purposes, we need
35+ // to use the port from the accessurl if the apphost doesn't have a port.
36+ // If the user specifies a port in the apphost, we will use that port instead.
37+ func SubdomainAppHost (apphost string ,accessURL * url.URL )string {
38+ if apphost == "" {
39+ return ""
40+ }
41+
42+ if apphost != "" && accessURL .Port ()!= "" {
43+ // This should always parse if we prepend a scheme. We should add
44+ // the access url port if the apphost doesn't have a port specified.
45+ appHostU ,err := url .Parse (fmt .Sprintf ("https://%s" ,apphost ))
46+ if err != nil || (err == nil && appHostU .Port ()== "" ) {
47+ apphost += fmt .Sprintf (":%s" ,accessURL .Port ())
48+ }
49+ }
50+
51+ return apphost
52+ }
53+
2354// ApplicationURL is a parsed application URL hostname.
2455type ApplicationURL struct {
2556Prefix string
@@ -140,9 +171,7 @@ func CompileHostnamePattern(pattern string) (*regexp.Regexp, error) {
140171if strings .Contains (pattern ,"http:" )|| strings .Contains (pattern ,"https:" ) {
141172return nil ,xerrors .Errorf ("hostname pattern must not contain a scheme: %q" ,pattern )
142173}
143- if strings .Contains (pattern ,":" ) {
144- return nil ,xerrors .Errorf ("hostname pattern must not contain a port: %q" ,pattern )
145- }
174+
146175if strings .HasPrefix (pattern ,"." )|| strings .HasSuffix (pattern ,"." ) {
147176return nil ,xerrors .Errorf ("hostname pattern must not start or end with a period: %q" ,pattern )
148177}
@@ -155,6 +184,16 @@ func CompileHostnamePattern(pattern string) (*regexp.Regexp, error) {
155184if ! strings .HasPrefix (pattern ,"*" ) {
156185return nil ,xerrors .Errorf ("hostname pattern must only contain an asterisk at the beginning: %q" ,pattern )
157186}
187+
188+ // If there is a hostname:port, we only care about the hostname. For hostname
189+ // pattern reasons, we do not actually care what port the client is requesting.
190+ // Any port provided here is used for generating urls for the ui, not for
191+ // validation.
192+ hostname ,_ ,err := net .SplitHostPort (pattern )
193+ if err == nil {
194+ pattern = hostname
195+ }
196+
158197for i ,label := range strings .Split (pattern ,"." ) {
159198if i == 0 {
160199// We have to allow the asterisk to be a valid hostname label, so