package streamhandler import ( "fmt" "net/http" "net/url" "strconv" "strings" "github.com/cloudflare/cloudflared/h2mux" "github.com/pkg/errors" ) const ( lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;" ) func FindCfRayHeader(h1 *http.Request) string { return h1.Header.Get("Cf-Ray") } func IsLBProbeRequest(req *http.Request) bool { return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix) } func createRequest(stream *h2mux.MuxedStream, url *url.URL) (*http.Request, error) { req, err := http.NewRequest(http.MethodGet, url.String(), h2mux.MuxedStreamReader{MuxedStream: stream}) if err != nil { return nil, errors.Wrap(err, "unexpected error from http.NewRequest") } err = H2RequestHeadersToH1Request(stream.Headers, req) if err != nil { return nil, errors.Wrap(err, "invalid request received") } return req, nil } // H2RequestHeadersToH1Request converts the HTTP/2 headers to an HTTP/1 Request // object. This includes conversion of the pseudo-headers into their closest // HTTP/1 equivalents. See https://tools.ietf.org/html/rfc7540#section-8.1.2.3 func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error { for _, header := range h2 { switch header.Name { case ":method": h1.Method = header.Value case ":scheme": // noop - use the preexisting scheme from h1.URL case ":authority": // Otherwise the host header will be based on the origin URL h1.Host = header.Value case ":path": // We don't want to be an "opinionated" proxy, so ideally we would use :path as-is. // However, this HTTP/1 Request object belongs to the Go standard library, // whose URL package makes some opinionated decisions about the encoding of // URL characters: see the docs of https://godoc.org/net/url#URL, // in particular the EscapedPath method https://godoc.org/net/url#URL.EscapedPath, // which is always used when computing url.URL.String(), whether we'd like it or not. // // Well, not *always*. We could circumvent this by using url.URL.Opaque. But // that would present unusual difficulties when using an HTTP proxy: url.URL.Opaque // is treated differently when HTTP_PROXY is set! // See https://github.com/golang/go/issues/5684#issuecomment-66080888 // // This means we are subject to the behavior of net/url's function `shouldEscape` // (as invoked with mode=encodePath): https://github.com/golang/go/blob/go1.12.7/src/net/url/url.go#L101 if header.Value == "*" { h1.URL.Path = "*" continue } // Due to the behavior of validation.ValidateUrl, h1.URL may // already have a partial value, with or without a trailing slash. base := h1.URL.String() base = strings.TrimRight(base, "/") // But we know :path begins with '/', because we handled '*' above - see RFC7540 url, err := url.Parse(base + header.Value) if err != nil { return errors.Wrap(err, fmt.Sprintf("invalid path '%v'", header.Value)) } h1.URL = url case "content-length": contentLength, err := strconv.ParseInt(header.Value, 10, 64) if err != nil { return fmt.Errorf("unparseable content length") } h1.ContentLength = contentLength default: h1.Header.Add(http.CanonicalHeaderKey(header.Name), header.Value) } } return nil }