@marcel91200 / caddy Mirror · read-only / commits / 80d7a356
Fork 0

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 +}