diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 849e8b0a209d33..cc6e5b9e80e9b3 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -785,6 +785,24 @@ type Config struct { // autoSessionTicketKeys is like sessionTicketKeys but is owned by the // auto-rotation logic. See Config.ticketKeys. autoSessionTicketKeys []ticketKey + + // HttpOnHttpsPortErrorResponseString is sent on an HTTPS connection + // which receives what looks like an HTTP request. + // "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\nClient sent an HTTP request to an HTTPS server.\n" + HttpOnHttpsPortErrorResponse string + + // HttpOnHttpsPortErrorRedirect is sent on an HTTPS connection + // which receives what looks like an HTTP request. + // If true, 307 redirect will be sent + HttpOnHttpsPortErrorRedirect bool + + // HttpOnHttpsPortErrorHandler handles HTTP requests sent to an HTTPS port. + // + // WriteString use: + // io.WriteString(conn, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\nClient sent an HTTP request to an HTTPS server.\n") + // Parse the request header use: + // http.ReadRequestForHttpOnHttpsPortErrorHandler(conn, recondBytes) + HttpOnHttpsPortErrorHandler func(conn net.Conn, recondBytes []byte, badRequestResponse string) } const ( diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index 0e4669866e5e98..c84c02b57d8db5 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -568,6 +568,9 @@ type RecordHeaderError struct { // RecordHeader contains the five bytes of TLS record header that // triggered the error. RecordHeader [5]byte + // RecondBytes contains all the bytes of the TLS record header that + // triggered the error. + RecondBytes []byte // Conn provides the underlying net.Conn in the case that a client // sent an initial handshake that didn't look like TLS. // It is nil if there's already been a handshake or a TLS alert has @@ -580,7 +583,8 @@ func (e RecordHeaderError) Error() string { return "tls: " + e.Msg } func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err RecordHeaderError) { err.Msg = msg err.Conn = conn - copy(err.RecordHeader[:], c.rawInput.Bytes()) + err.RecondBytes = c.rawInput.Bytes() + copy(err.RecordHeader[:], err.RecondBytes) return err } diff --git a/src/net/http/server.go b/src/net/http/server.go index 0ba88d1119e4f9..06a43e78f14c86 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -1923,7 +1923,7 @@ func (c *conn) serve(ctx context.Context) { // TLS, assume they're speaking plaintext HTTP and write a // 400 response on the TLS conn's underlying net.Conn. if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) { - io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n") + c.httpOnHttpsPortErrorHandler(re.Conn, re.RecondBytes) re.Conn.Close() return } @@ -3841,3 +3841,68 @@ func MaxBytesHandler(h Handler, n int64) Handler { h.ServeHTTP(w, &r2) }) } + +// net/http: configurable error message for Client sent an HTTP request to an HTTPS server. +// https://go.dev/issue/49310 +func (c *conn) httpOnHttpsPortErrorHandler(conn net.Conn, recondBytes []byte) { + // Read Response string + badRequestResponse := c.server.TLSConfig.HttpOnHttpsPortErrorResponse + if badRequestResponse == "" { + badRequestResponse = "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\nClient sent an HTTP request to an HTTPS server.\n" + } + + // Handler + if handler := c.server.TLSConfig.HttpOnHttpsPortErrorHandler; handler != nil { + handler(conn, recondBytes, badRequestResponse) + return + } + + // Redirect + if c.server.TLSConfig.HttpOnHttpsPortErrorRedirect { + // Read Header + req, _, err := ReadRequestForHttpOnHttpsPortErrorHandler(conn, recondBytes) + if err != nil { + io.WriteString(conn, badRequestResponse) + return + } + + // Send Redirect + io.WriteString(conn, fmt.Sprintf( + "HTTP/1.1 307 Temporary Redirect\r\nLocation: https://%s%s\r\nConnection: close\r\n\r\nClient sent an HTTP request to an HTTPS server.\n", + req.Host, req.URL.Path, + )) + return + } + + // Send Response string + io.WriteString(conn, badRequestResponse) +} + +func ReadRequestForHttpOnHttpsPortErrorHandler(conn net.Conn, recondBytes []byte) (req *Request, reqBytes []byte, err error) { + // Max 4KB + if len(recondBytes) > 4096 { + return nil, recondBytes, errors.New("recondBytes too long") + } + + // Content length may be insufficient, continue reading. + if len(recondBytes) < 4096 && !bytes.Contains(recondBytes, []byte("\r\n\r\n")) { + b := make([]byte, 4096-len(recondBytes)) + // Set Timeout 1s + conn.SetReadDeadline(time.Now().Add(time.Duration(1 * time.Second))) + n, err := conn.Read(b) + if err != nil { + return nil, recondBytes, err + } + recondBytes = append(recondBytes, b[:n]...) + } + + // Read Request + req, err = ReadRequest(bufio.NewReader(bytes.NewReader(recondBytes))) + if err != nil { + return nil, recondBytes, err + } + if req.Host == "" { + return nil, recondBytes, errors.New("missing required Host header") + } + return req, recondBytes, nil +}