Repository: caddyserver/caddy
File: modules/caddyhttp/httpredirectlistener.go (Go)

Code before:
		if !firstBytesLookLikeHTTP(firstBytes) {
			return 0, nil
		}

		// From now on, we can be almost certain the request is HTTP.
		// The returned error will be non nil and caller are expected to
		// close the connection.

		// Set the read limit, io.MultiReader is needed because
		// when resetting, *bufio.Reader discards buffered data.
		buffered, _ := c.r.Peek(c.r.Buffered())
		mr := io.MultiReader(bytes.NewReader(buffered), c.Conn)
		c.r.Reset(io.LimitReader(mr, c.limit))

		// Parse the HTTP request, so we can get the Host and URL to redirect to.
		req, err := http.ReadRequest(c.r)
		if err != nil {
			return 0, fmt.Errorf("couldn't read HTTP request")
		}

		// Build the redirect response, using the same Host and URL,
		// but replacing the scheme with https.
		headers := make(http.Header)
		headers.Add("Location", "https://"+req.Host+req.URL.String())
		resp := &http.Response{
			Proto:      "HTTP/1.0",
			Status:     "308 Permanent Redirect",
			StatusCode: 308,
			ProtoMajor: 1,
			ProtoMinor: 0,
			Header:     headers,
		}

		err = resp.Write(c.Conn)
		if err != nil {
			return 0, fmt.Errorf("couldn't write HTTP->HTTPS redirect")
		}

		return 0, fmt.Errorf("redirected HTTP request on HTTPS port")
	}
	return c.r.Read(p)
}

// firstBytesLookLikeHTTP reports whether a TLS record header
// looks like it might've been a misdirected plaintext HTTP request.
func firstBytesLookLikeHTTP(hdr []byte) bool {
	switch string(hdr[:5]) {
	case "GET /", "HEAD ", "POST ", "PUT /", "OPTIO":
		return true
	}
	return false

Review comment (bug):
Why remove the conn close? The connection can no longer be used because the wrong protocol was sent to it 🤔

Code after:
	// TLS bytes, and we don't need to do anything.
	if !firstBytesLookLikeHTTP(firstBytes) {
		return c.r.Read(p)
	}

	// From now on, we can be almost certain the request is HTTP.
	// The returned error will be non nil and caller are expected to
	// close the connection.

	// Set the read limit, io.MultiReader is needed because
	// when resetting, *bufio.Reader discards buffered data.
	buffered, _ := c.r.Peek(c.r.Buffered())
	mr := io.MultiReader(bytes.NewReader(buffered), c.Conn)
	c.r.Reset(io.LimitReader(mr, c.limit))

	// Parse the HTTP request, so we can get the Host and URL to redirect to.
	req, err := http.ReadRequest(c.r)
	if err != nil {
		return 0, fmt.Errorf("couldn't read HTTP request")
	}

	// Build the redirect response, using the same Host and URL,
	// but replacing the scheme with https.
	headers := make(http.Header)
	headers.Add("Location", "https://"+req.Host+req.URL.String())
	resp := &http.Response{
		Proto:      "HTTP/1.0",
		Status:     "308 Permanent Redirect",
		StatusCode: 308,
		ProtoMajor: 1,
		ProtoMinor: 0,
		Header:     headers,
	}

	err = resp.Write(c.Conn)
	if err != nil {
		return 0, fmt.Errorf("couldn't write HTTP->HTTPS redirect")
	}

	return 0, fmt.Errorf("redirected HTTP request on HTTPS port")
}

// firstBytesLookLikeHTTP reports whether a TLS record header
// looks like it might've been a misdirected plaintext HTTP request.
func firstBytesLookLikeHTTP(hdr []byte) bool {
	switch string(hdr[:5]) {
	case "GET /", "HEAD ", "POST ", "PUT /", "OPTIO":
		return true
	}
	return false
}