http2: support CONNECT requests

Support CONNECT requests in both the server & transport.

See https://httpwg.github.io/specs/rfc7540.html#CONNECT

When I bundle this into the main Go repo I will also add h1-vs-h2
compatibility tests there, making sure they match behavior. (I now
expect that they do match)

Updates golang/go#13717

Change-Id: I0c65ad47b029419027efb616fed3d8e0e2a363f4
Reviewed-on: https://go-review.googlesource.com/18266
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/http2/server.go b/http2/server.go
index 9326eb6..eaed248 100644
--- a/http2/server.go
+++ b/http2/server.go
@@ -1545,7 +1545,17 @@
 func (sc *serverConn) newWriterAndRequest() (*responseWriter, *http.Request, error) {
 	sc.serveG.check()
 	rp := &sc.req
-	if rp.invalidHeader || rp.method == "" || rp.path == "" ||
+
+	if rp.invalidHeader {
+		return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
+	}
+
+	isConnect := rp.method == "CONNECT"
+	if isConnect {
+		if rp.path != "" || rp.scheme != "" || rp.authority == "" {
+			return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
+		}
+	} else if rp.method == "" || rp.path == "" ||
 		(rp.scheme != "https" && rp.scheme != "http") {
 		// See 8.1.2.6 Malformed Requests and Responses:
 		//
@@ -1559,12 +1569,14 @@
 		// pseudo-header fields"
 		return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
 	}
+
 	bodyOpen := rp.stream.state == stateOpen
 	if rp.method == "HEAD" && bodyOpen {
 		// HEAD requests can't have bodies
 		return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
 	}
 	var tlsState *tls.ConnectionState // nil if not scheme https
+
 	if rp.scheme == "https" {
 		tlsState = sc.tlsState
 	}
@@ -1605,18 +1617,26 @@
 		stream:        rp.stream,
 		needsContinue: needsContinue,
 	}
-	// TODO: handle asterisk '*' requests + test
-	url, err := url.ParseRequestURI(rp.path)
-	if err != nil {
-		// TODO: find the right error code?
-		return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
+	var url_ *url.URL
+	var requestURI string
+	if isConnect {
+		url_ = &url.URL{Host: rp.authority}
+		requestURI = rp.authority // mimic HTTP/1 server behavior
+	} else {
+		var err error
+		// TODO: handle asterisk '*' requests + test
+		url_, err = url.ParseRequestURI(rp.path)
+		if err != nil {
+			return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
+		}
+		requestURI = rp.path
 	}
 	req := &http.Request{
 		Method:     rp.method,
-		URL:        url,
+		URL:        url_,
 		RemoteAddr: sc.remoteAddrStr,
 		Header:     rp.header,
-		RequestURI: rp.path,
+		RequestURI: requestURI,
 		Proto:      "HTTP/2.0",
 		ProtoMajor: 2,
 		ProtoMinor: 0,