cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2018.10.3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

validation/validation.go

179lines · modecode

1package validation
2
3import (
4 "fmt"
5 "net"
6 "net/url"
7 "strings"
8
9 "github.com/pkg/errors"
10 "golang.org/x/net/idna"
11 "net/http"
12)
13
14const defaultScheme = "http"
15
16var supportedProtocol = [2]string{"http", "https"}
17
18func ValidateHostname(hostname string) (string, error) {
19 if hostname == "" {
20 return "", nil
21 }
22 // users gives url(contains schema) not just hostname
23 if strings.Contains(hostname, ":") || strings.Contains(hostname, "%3A") {
24 unescapeHostname, err := url.PathUnescape(hostname)
25 if err != nil {
26 return "", fmt.Errorf("Hostname(actually a URL) %s has invalid escape characters %s", hostname, unescapeHostname)
27 }
28 hostnameToURL, err := url.Parse(unescapeHostname)
29 if err != nil {
30 return "", fmt.Errorf("Hostname(actually a URL) %s has invalid format %s", hostname, hostnameToURL)
31 }
32 asciiHostname, err := idna.ToASCII(hostnameToURL.Hostname())
33 if err != nil {
34 return "", fmt.Errorf("Hostname(actually a URL) %s has invalid ASCII encdoing %s", hostname, asciiHostname)
35 }
36 return asciiHostname, nil
37 }
38
39 asciiHostname, err := idna.ToASCII(hostname)
40 if err != nil {
41 return "", fmt.Errorf("Hostname %s has invalid ASCII encdoing %s", hostname, asciiHostname)
42 }
43 hostnameToURL, err := url.Parse(asciiHostname)
44 if err != nil {
45 return "", fmt.Errorf("Hostname %s is not valid", hostnameToURL)
46 }
47 return hostnameToURL.RequestURI(), nil
48
49}
50
51func ValidateUrl(originUrl string) (string, error) {
52 if originUrl == "" {
53 return "", fmt.Errorf("URL should not be empty")
54 }
55
56 if net.ParseIP(originUrl) != nil {
57 return validateIP("", originUrl, "")
58 } else if strings.HasPrefix(originUrl, "[") && strings.HasSuffix(originUrl, "]") {
59 // ParseIP doesn't recoginze [::1]
60 return validateIP("", originUrl[1:len(originUrl)-1], "")
61 }
62
63 host, port, err := net.SplitHostPort(originUrl)
64 // user might pass in an ip address like 127.0.0.1
65 if err == nil && net.ParseIP(host) != nil {
66 return validateIP("", host, port)
67 }
68
69 unescapedUrl, err := url.PathUnescape(originUrl)
70 if err != nil {
71 return "", fmt.Errorf("URL %s has invalid escape characters %s", originUrl, unescapedUrl)
72 }
73
74 parsedUrl, err := url.Parse(unescapedUrl)
75 if err != nil {
76 return "", fmt.Errorf("URL %s has invalid format", originUrl)
77 }
78
79 // if the url is in the form of host:port, IsAbs() will think host is the schema
80 var hostname string
81 hasScheme := parsedUrl.IsAbs() && parsedUrl.Host != ""
82 if hasScheme {
83 err := validateScheme(parsedUrl.Scheme)
84 if err != nil {
85 return "", err
86 }
87 // The earlier check for ip address will miss the case http://[::1]
88 // and http://[::1]:8080
89 if net.ParseIP(parsedUrl.Hostname()) != nil {
90 return validateIP(parsedUrl.Scheme, parsedUrl.Hostname(), parsedUrl.Port())
91 }
92 hostname, err = ValidateHostname(parsedUrl.Hostname())
93 if err != nil {
94 return "", fmt.Errorf("URL %s has invalid format", originUrl)
95 }
96 if parsedUrl.Port() != "" {
97 return fmt.Sprintf("%s://%s", parsedUrl.Scheme, net.JoinHostPort(hostname, parsedUrl.Port())), nil
98 }
99 return fmt.Sprintf("%s://%s", parsedUrl.Scheme, hostname), nil
100 } else {
101 if host == "" {
102 hostname, err = ValidateHostname(originUrl)
103 if err != nil {
104 return "", fmt.Errorf("URL no %s has invalid format", originUrl)
105 }
106 return fmt.Sprintf("%s://%s", defaultScheme, hostname), nil
107 } else {
108 hostname, err = ValidateHostname(host)
109 if err != nil {
110 return "", fmt.Errorf("URL %s has invalid format", originUrl)
111 }
112 return fmt.Sprintf("%s://%s", defaultScheme, net.JoinHostPort(hostname, port)), nil
113 }
114 }
115
116}
117
118func validateScheme(scheme string) error {
119 for _, protocol := range supportedProtocol {
120 if scheme == protocol {
121 return nil
122 }
123 }
124 return fmt.Errorf("Currently Argo Tunnel does not support %s protocol.", scheme)
125}
126
127func validateIP(scheme, host, port string) (string, error) {
128 if scheme == "" {
129 scheme = defaultScheme
130 }
131 if port != "" {
132 return fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(host, port)), nil
133 } else if strings.Contains(host, ":") {
134 // IPv6
135 return fmt.Sprintf("%s://[%s]", scheme, host), nil
136 }
137 return fmt.Sprintf("%s://%s", scheme, host), nil
138}
139
140func ValidateHTTPService(originURL string, transport http.RoundTripper) error {
141 parsedURL, err := url.Parse(originURL)
142 if err != nil {
143 return err
144 }
145
146 client := &http.Client{Transport: transport}
147
148 initialResponse, initialErr := client.Get(parsedURL.String())
149 if initialErr != nil || initialResponse.StatusCode != http.StatusOK {
150 // Attempt the same endpoint via the other protocol (http/https); maybe we have better luck?
151 oldScheme := parsedURL.Scheme
152 parsedURL.Scheme = toggleProtocol(parsedURL.Scheme)
153
154 secondResponse, _ := client.Get(parsedURL.String())
155
156 if secondResponse != nil && secondResponse.StatusCode == http.StatusOK { // Worked this time--advise the user to switch protocols
157 return errors.Errorf(
158 "%s doesn't seem to work over %s, but does seem to work over %s. Consider changing the origin URL to %s",
159 parsedURL.Hostname(),
160 oldScheme,
161 parsedURL.Scheme,
162 parsedURL,
163 )
164 }
165 }
166
167 return initialErr
168}
169
170func toggleProtocol(httpProtocol string) string {
171 switch httpProtocol {
172 case "http":
173 return "https"
174 case "https":
175 return "http"
176 default:
177 return httpProtocol
178 }
179}
180