Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 00:15 UTC
caddyhttp: Redirect HTTP requests on the HTTPS port to https:// (#4313)
* caddyhttp: Redirect HTTP requests on the HTTPS port to https:// * Apply suggestions from code review Co-authored-by: Matt Holt <mholt@users.noreply.github.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Francis Lavoie committed
Jan 5, 2022 at 20:01 UTC
80d7a356b3443e0a994e5d6abfa6082ba3d5e6e7
2 files changed
+119
modules/caddyhttp/app.go
+5
@@ -343,6 +343,11 @@ func (app *App) Start() error {
343
// enable TLS if there is a policy and if this is not the HTTP port
344
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
345
if useTLS {
346
+ // create HTTP redirect wrapper, which detects if
347
+ // the request had HTTP bytes on the HTTPS port, and
348
+ // triggers a redirect if so.
349
+ ln = &httpRedirectListener{Listener: ln}
350
+
351
// create TLS listener
352
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
353
ln = tls.NewListener(ln, tlsCfg)
modules/caddyhttp/httpredirectlistener.go
new
+114
@@ -0,0 +1,114 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddyhttp
16
+
17
+import (
18
+ "bufio"
19
+ "fmt"
20
+ "net"
21
+ "net/http"
22
+ "sync"
23
+)
24
+
25
+// httpRedirectListener is listener that checks the first few bytes
26
+// of the request when the server is intended to accept HTTPS requests,
27
+// to respond to an HTTP request with a redirect.
28
+type httpRedirectListener struct {
29
+ net.Listener
30
+}
31
+
32
+// Accept waits for and returns the next connection to the listener,
33
+// wrapping it with a httpRedirectConn.
34
+func (l *httpRedirectListener) Accept() (net.Conn, error) {
35
+ c, err := l.Listener.Accept()
36
+ if err != nil {
37
+ return nil, err
38
+ }
39
+
40
+ return &httpRedirectConn{
41
+ Conn: c,
42
+ r: bufio.NewReader(c),
43
+ }, nil
44
+}
45
+
46
+type httpRedirectConn struct {
47
+ net.Conn
48
+ once sync.Once
49
+ r *bufio.Reader
50
+}
51
+
52
+// Read tries to peek at the first few bytes of the request, and if we get
53
+// an error reading the headers, and that error was due to the bytes looking
54
+// like an HTTP request, then we perform a HTTP->HTTPS redirect on the same
55
+// port as the original connection.
56
+func (c *httpRedirectConn) Read(p []byte) (int, error) {
57
+ var errReturn error
58
+ c.once.Do(func() {
59
+ firstBytes, err := c.r.Peek(5)
60
+ if err != nil {
61
+ return
62
+ }
63
+
64
+ // If the request doesn't look like HTTP, then it's probably
65
+ // TLS bytes and we don't need to do anything.
66
+ if !firstBytesLookLikeHTTP(firstBytes) {
67
+ return
68
+ }
69
+
70
+ // Parse the HTTP request, so we can get the Host and URL to redirect to.
71
+ req, err := http.ReadRequest(c.r)
72
+ if err != nil {
73
+ return
74
+ }
75
+
76
+ // Build the redirect response, using the same Host and URL,
77
+ // but replacing the scheme with https.
78
+ headers := make(http.Header)
79
+ headers.Add("Location", "https://"+req.Host+req.URL.String())
80
+ resp := &http.Response{
81
+ Proto: "HTTP/1.0",
82
+ Status: "308 Permanent Redirect",
83
+ StatusCode: 308,
84
+ ProtoMajor: 1,
85
+ ProtoMinor: 0,
86
+ Header: headers,
87
+ }
88
+
89
+ err = resp.Write(c.Conn)
90
+ if err != nil {
91
+ errReturn = fmt.Errorf("couldn't write HTTP->HTTPS redirect")
92
+ return
93
+ }
94
+
95
+ errReturn = fmt.Errorf("redirected HTTP request on HTTPS port")
96
+ c.Conn.Close()
97
+ })
98
+
99
+ if errReturn != nil {
100
+ return 0, errReturn
101
+ }
102
+
103
+ return c.r.Read(p)
104
+}
105
+
106
+// firstBytesLookLikeHTTP reports whether a TLS record header
107
+// looks like it might've been a misdirected plaintext HTTP request.
108
+func firstBytesLookLikeHTTP(hdr []byte) bool {
109
+ switch string(hdr[:5]) {
110
+ case "GET /", "HEAD ", "POST ", "PUT /", "OPTIO":
111
+ return true
112
+ }
113
+ return false
114
+}