-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathscheme.go
More file actions
134 lines (121 loc) · 4.98 KB
/
Copy pathscheme.go
File metadata and controls
134 lines (121 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"net"
"net/http"
"strings"
)
// SchemeExtractor is a function to determine the scheme (for example `http` or `https`) of http.Request.
// Set appropriate one to Echo#SchemeExtractor.
//
// When Echo#SchemeExtractor is not set, Context.Scheme uses ExtractSchemeFromHeaders with the default trust options.
type SchemeExtractor func(*http.Request) string
// ExtractSchemeDirect returns a SchemeExtractor that uses only the actual connection: `https` for TLS connections
// and `http` otherwise. Forwarding headers are ignored.
// Use this if your server faces the internet directly (i.e.: uses no proxy).
func ExtractSchemeDirect() SchemeExtractor {
return extractSchemeDirect
}
func extractSchemeDirect(req *http.Request) string {
if req.TLS != nil {
return "https"
}
return "http"
}
// ExtractSchemeFromHeaders returns a SchemeExtractor that uses the `X-Forwarded-Proto`, `X-Forwarded-Protocol`,
// `X-Forwarded-Ssl` and `X-Url-Scheme` headers only when the request comes directly from a trusted address
// (http.Request.RemoteAddr). For requests from other addresses the headers are ignored and the scheme of the actual
// connection is used.
//
// By default, loopback, link-local and private network addresses and unix socket peers are trusted. Use TrustOption
// to change this, for example TrustIPRange to trust a proxy that connects from a public address (such as a CDN or a
// cloud load balancer). TrustLoopback(false) also stops trusting unix socket peers.
//
// When `X-Forwarded-Proto` is present, the other headers are ignored, and the last value is used. The trusted proxy
// must therefore set (overwrite) `X-Forwarded-Proto` rather than pass through the value sent by the client.
//
// This is the default strategy when Echo#SchemeExtractor is not set.
func ExtractSchemeFromHeaders(options ...TrustOption) SchemeExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
return extractScheme(req, checker)
}
}
// LegacySchemeExtractor returns a SchemeExtractor that uses the forwarding headers from any client. This was the
// behavior of Context.Scheme before the address of the client was checked.
//
// It is not safe against spoofing unless every request passes through a proxy that sets or removes the
// `X-Forwarded-Proto`, `X-Forwarded-Protocol`, `X-Forwarded-Ssl` and `X-Url-Scheme` headers.
// Use ExtractSchemeFromHeaders instead.
func LegacySchemeExtractor() SchemeExtractor {
return legacySchemeExtractor
}
func legacySchemeExtractor(req *http.Request) string {
if req.TLS != nil {
return "https"
}
return schemeFromHeaders(req)
}
// defaultSchemeChecker is used by Context.Scheme when Echo#SchemeExtractor is not set.
var defaultSchemeChecker = newIPChecker(nil)
func extractScheme(req *http.Request, checker *ipChecker) string {
if req.TLS != nil {
return "https"
}
if !isTrustedPeer(req.RemoteAddr, checker) {
return "http"
}
return schemeFromHeaders(req)
}
// isTrustedPeer reports whether the direct peer of the request is trusted to set forwarding headers.
func isTrustedPeer(remoteAddr string, checker *ipChecker) bool {
// Unix socket peers are on the same host, so they are trusted like loopback addresses. net/http reports them
// as an empty string, as "@" or "@name" (Linux, unnamed or abstract socket) or as the path of a bound socket.
if remoteAddr == "" || remoteAddr[0] == '@' || remoteAddr[0] == '/' {
return checker.trustLoopback
}
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
host = remoteAddr
}
if i := strings.IndexByte(host, '%'); i != -1 { // IPv6 zone, e.g. "fe80::1%eth0"
host = host[:i]
}
ip := net.ParseIP(host)
return ip != nil && checker.trust(ip)
}
func schemeFromHeaders(req *http.Request) string {
// When X-Forwarded-Proto is present it is the only header used: a trusted proxy sets it, and the other headers
// might be client-supplied values that the proxy did not remove. With several values (repeated header lines or a
// comma-separated list) the last one was added by the nearest proxy.
if values := req.Header.Values(HeaderXForwardedProto); len(values) > 0 && values[len(values)-1] != "" {
last := values[len(values)-1]
if i := strings.LastIndexByte(last, ','); i != -1 {
last = last[i+1:]
}
if scheme, ok := canonicalProto(strings.TrimSpace(last)); ok {
return scheme
}
return "http"
}
if scheme, ok := canonicalProto(req.Header.Get(HeaderXForwardedProtocol)); ok {
return scheme
}
if ssl := req.Header.Get(HeaderXForwardedSsl); ssl == "on" {
return "https"
}
if scheme, ok := canonicalProto(req.Header.Get(HeaderXUrlScheme)); ok {
return scheme
}
return "http"
}
// canonicalProto returns the lowercase form of proto if it is `http`, `https`, `ws` or `wss` (in any case).
func canonicalProto(proto string) (string, bool) {
for _, p := range [...]string{"http", "https", "ws", "wss"} {
if strings.EqualFold(proto, p) {
return p, true
}
}
return "", false
}