// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// HTTP reverse proxy handler

package httputil

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
)

// A ProxyRequest contains a request to be rewritten by a [ReverseProxy].
type ProxyRequest struct {
	// In is the request received by the proxy.
	// The Rewrite function must not modify In.
	In *http.Request

	// Out is the request which will be sent by the proxy.
	// The Rewrite function may modify or replace this request.
	// Hop-by-hop headers are removed from this request
	// before Rewrite is called.
	Out *http.Request
}

// SetURL routes the outbound request to the scheme, host, and base path
// provided in target. If the target's path is "/base" and the incoming
// request was for "/dir", the target request will be for "/base/dir".
//
// SetURL rewrites the outbound Host header to match the target's host.
// To preserve the inbound request's Host header (the default behavior
// of [NewSingleHostReverseProxy]):
//
//	rewriteFunc := func(r *httputil.ProxyRequest) {
//		r.SetURL(url)
//		r.Out.Host = r.In.Host
//	}
func ( *ProxyRequest) ( *url.URL) {
	rewriteRequestURL(.Out, )
	.Out.Host = ""
}

// SetXForwarded sets the X-Forwarded-For, X-Forwarded-Host, and
// X-Forwarded-Proto headers of the outbound request.
//
//   - The X-Forwarded-For header is set to the client IP address.
//   - The X-Forwarded-Host header is set to the host name requested
//     by the client.
//   - The X-Forwarded-Proto header is set to "http" or "https", depending
//     on whether the inbound request was made on a TLS-enabled connection.
//
// If the outbound request contains an existing X-Forwarded-For header,
// SetXForwarded appends the client IP address to it. To append to the
// inbound request's X-Forwarded-For header (the default behavior of
// [ReverseProxy] when using a Director function), copy the header
// from the inbound request before calling SetXForwarded:
//
//	rewriteFunc := func(r *httputil.ProxyRequest) {
//		r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
//		r.SetXForwarded()
//	}
func ( *ProxyRequest) () {
	, ,  := net.SplitHostPort(.In.RemoteAddr)
	if  == nil {
		 := .Out.Header["X-Forwarded-For"]
		if len() > 0 {
			 = strings.Join(, ", ") + ", " + 
		}
		.Out.Header.Set("X-Forwarded-For", )
	} else {
		.Out.Header.Del("X-Forwarded-For")
	}
	.Out.Header.Set("X-Forwarded-Host", .In.Host)
	if .In.TLS == nil {
		.Out.Header.Set("X-Forwarded-Proto", "http")
	} else {
		.Out.Header.Set("X-Forwarded-Proto", "https")
	}
}

// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
//
// 1xx responses are forwarded to the client if the underlying
// transport supports ClientTrace.Got1xxResponse.
type ReverseProxy struct {
	// Rewrite must be a function which modifies
	// the request into a new request to be sent
	// using Transport. Its response is then copied
	// back to the original client unmodified.
	// Rewrite must not access the provided ProxyRequest
	// or its contents after returning.
	//
	// The Forwarded, X-Forwarded, X-Forwarded-Host,
	// and X-Forwarded-Proto headers are removed from the
	// outbound request before Rewrite is called. See also
	// the ProxyRequest.SetXForwarded method.
	//
	// Unparsable query parameters are removed from the
	// outbound request before Rewrite is called.
	// The Rewrite function may copy the inbound URL's
	// RawQuery to the outbound URL to preserve the original
	// parameter string. Note that this can lead to security
	// issues if the proxy's interpretation of query parameters
	// does not match that of the downstream server.
	//
	// At most one of Rewrite or Director may be set.
	Rewrite func(*ProxyRequest)

	// Director is a function which modifies
	// the request into a new request to be sent
	// using Transport. Its response is then copied
	// back to the original client unmodified.
	// Director must not access the provided Request
	// after returning.
	//
	// By default, the X-Forwarded-For header is set to the
	// value of the client IP address. If an X-Forwarded-For
	// header already exists, the client IP is appended to the
	// existing values. As a special case, if the header
	// exists in the Request.Header map but has a nil value
	// (such as when set by the Director func), the X-Forwarded-For
	// header is not modified.
	//
	// To prevent IP spoofing, be sure to delete any pre-existing
	// X-Forwarded-For header coming from the client or
	// an untrusted proxy.
	//
	// Hop-by-hop headers are removed from the request after
	// Director returns, which can remove headers added by
	// Director. Use a Rewrite function instead to ensure
	// modifications to the request are preserved.
	//
	// Unparsable query parameters are removed from the outbound
	// request if Request.Form is set after Director returns.
	//
	// At most one of Rewrite or Director may be set.
	Director func(*http.Request)

	// The transport used to perform proxy requests.
	// If nil, http.DefaultTransport is used.
	Transport http.RoundTripper

	// FlushInterval specifies the flush interval
	// to flush to the client while copying the
	// response body.
	// If zero, no periodic flushing is done.
	// A negative value means to flush immediately
	// after each write to the client.
	// The FlushInterval is ignored when ReverseProxy
	// recognizes a response as a streaming response, or
	// if its ContentLength is -1; for such responses, writes
	// are flushed to the client immediately.
	FlushInterval time.Duration

	// ErrorLog specifies an optional logger for errors
	// that occur when attempting to proxy the request.
	// If nil, logging is done via the log package's standard logger.
	ErrorLog *log.Logger

	// BufferPool optionally specifies a buffer pool to
	// get byte slices for use by io.CopyBuffer when
	// copying HTTP response bodies.
	BufferPool BufferPool

	// ModifyResponse is an optional function that modifies the
	// Response from the backend. It is called if the backend
	// returns a response at all, with any HTTP status code.
	// If the backend is unreachable, the optional ErrorHandler is
	// called without any call to ModifyResponse.
	//
	// If ModifyResponse returns an error, ErrorHandler is called
	// with its error value. If ErrorHandler is nil, its default
	// implementation is used.
	ModifyResponse func(*http.Response) error

	// ErrorHandler is an optional function that handles errors
	// reaching the backend or errors from ModifyResponse.
	//
	// If nil, the default is to log the provided error and return
	// a 502 Status Bad Gateway response.
	ErrorHandler func(http.ResponseWriter, *http.Request, error)
}

// A BufferPool is an interface for getting and returning temporary
// byte slices for use by [io.CopyBuffer].
type BufferPool interface {
	Get() []byte
	Put([]byte)
}

func singleJoiningSlash(,  string) string {
	 := strings.HasSuffix(, "/")
	 := strings.HasPrefix(, "/")
	switch {
	case  && :
		return  + [1:]
	case ! && !:
		return  + "/" + 
	}
	return  + 
}

func joinURLPath(,  *url.URL) (,  string) {
	if .RawPath == "" && .RawPath == "" {
		return singleJoiningSlash(.Path, .Path), ""
	}
	// Same as singleJoiningSlash, but uses EscapedPath to determine
	// whether a slash should be added
	 := .EscapedPath()
	 := .EscapedPath()

	 := strings.HasSuffix(, "/")
	 := strings.HasPrefix(, "/")

	switch {
	case  && :
		return .Path + .Path[1:],  + [1:]
	case ! && !:
		return .Path + "/" + .Path,  + "/" + 
	}
	return .Path + .Path,  + 
}

// NewSingleHostReverseProxy returns a new [ReverseProxy] that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
//
// NewSingleHostReverseProxy does not rewrite the Host header.
//
// To customize the ReverseProxy behavior beyond what
// NewSingleHostReverseProxy provides, use ReverseProxy directly
// with a Rewrite function. The ProxyRequest SetURL method
// may be used to route the outbound request. (Note that SetURL,
// unlike NewSingleHostReverseProxy, rewrites the Host header
// of the outbound request by default.)
//
//	proxy := &ReverseProxy{
//		Rewrite: func(r *ProxyRequest) {
//			r.SetURL(target)
//			r.Out.Host = r.In.Host // if desired
//		},
//	}
func ( *url.URL) *ReverseProxy {
	 := func( *http.Request) {
		rewriteRequestURL(, )
	}
	return &ReverseProxy{Director: }
}

func rewriteRequestURL( *http.Request,  *url.URL) {
	 := .RawQuery
	.URL.Scheme = .Scheme
	.URL.Host = .Host
	.URL.Path, .URL.RawPath = joinURLPath(, .URL)
	if  == "" || .URL.RawQuery == "" {
		.URL.RawQuery =  + .URL.RawQuery
	} else {
		.URL.RawQuery =  + "&" + .URL.RawQuery
	}
}

func copyHeader(,  http.Header) {
	for ,  := range  {
		for ,  := range  {
			.Add(, )
		}
	}
}

// Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
// compatibility.
var hopHeaders = []string{
	"Connection",
	"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
	"Keep-Alive",
	"Proxy-Authenticate",
	"Proxy-Authorization",
	"Te",      // canonicalized version of "TE"
	"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
	"Transfer-Encoding",
	"Upgrade",
}

func ( *ReverseProxy) ( http.ResponseWriter,  *http.Request,  error) {
	.logf("http: proxy error: %v", )
	.WriteHeader(http.StatusBadGateway)
}

func ( *ReverseProxy) () func(http.ResponseWriter, *http.Request, error) {
	if .ErrorHandler != nil {
		return .ErrorHandler
	}
	return .defaultErrorHandler
}

// modifyResponse conditionally runs the optional ModifyResponse hook
// and reports whether the request should proceed.
func ( *ReverseProxy) ( http.ResponseWriter,  *http.Response,  *http.Request) bool {
	if .ModifyResponse == nil {
		return true
	}
	if  := .ModifyResponse();  != nil {
		.Body.Close()
		.getErrorHandler()(, , )
		return false
	}
	return true
}

func ( *ReverseProxy) ( http.ResponseWriter,  *http.Request) {
	 := .Transport
	if  == nil {
		 = http.DefaultTransport
	}

	 := .Context()
	if .Done() != nil {
		// CloseNotifier predates context.Context, and has been
		// entirely superseded by it. If the request contains
		// a Context that carries a cancellation signal, don't
		// bother spinning up a goroutine to watch the CloseNotify
		// channel (if any).
		//
		// If the request Context has a nil Done channel (which
		// means it is either context.Background, or a custom
		// Context implementation with no cancellation signal),
		// then consult the CloseNotifier if available.
	} else if ,  := .(http.CloseNotifier);  {
		var  context.CancelFunc
		,  = context.WithCancel()
		defer ()
		 := .CloseNotify()
		go func() {
			select {
			case <-:
				()
			case <-.Done():
			}
		}()
	}

	 := .Clone()
	if .ContentLength == 0 {
		.Body = nil // Issue 16036: nil Body for http.Transport retries
	}
	if .Body != nil {
		// Reading from the request body after returning from a handler is not
		// allowed, and the RoundTrip goroutine that reads the Body can outlive
		// this handler. This can lead to a crash if the handler panics (see
		// Issue 46866). Although calling Close doesn't guarantee there isn't
		// any Read in flight after the handle returns, in practice it's safe to
		// read after closing it.
		defer .Body.Close()
	}
	if .Header == nil {
		.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
	}

	if (.Director != nil) == (.Rewrite != nil) {
		.getErrorHandler()(, , errors.New("ReverseProxy must have exactly one of Director or Rewrite set"))
		return
	}

	if .Director != nil {
		.Director()
		if .Form != nil {
			.URL.RawQuery = cleanQueryParams(.URL.RawQuery)
		}
	}
	.Close = false

	 := upgradeType(.Header)
	if !ascii.IsPrint() {
		.getErrorHandler()(, , fmt.Errorf("client tried to switch to invalid protocol %q", ))
		return
	}
	removeHopByHopHeaders(.Header)

	// Issue 21096: tell backend applications that care about trailer support
	// that we support trailers. (We do, but we don't go out of our way to
	// advertise that unless the incoming client request thought it was worth
	// mentioning.) Note that we look at req.Header, not outreq.Header, since
	// the latter has passed through removeHopByHopHeaders.
	if httpguts.HeaderValuesContainsToken(.Header["Te"], "trailers") {
		.Header.Set("Te", "trailers")
	}

	// After stripping all the hop-by-hop connection headers above, add back any
	// necessary for protocol upgrades, such as for websockets.
	if  != "" {
		.Header.Set("Connection", "Upgrade")
		.Header.Set("Upgrade", )
	}

	if .Rewrite != nil {
		// Strip client-provided forwarding headers.
		// The Rewrite func may use SetXForwarded to set new values
		// for these or copy the previous values from the inbound request.
		.Header.Del("Forwarded")
		.Header.Del("X-Forwarded-For")
		.Header.Del("X-Forwarded-Host")
		.Header.Del("X-Forwarded-Proto")

		// Remove unparsable query parameters from the outbound request.
		.URL.RawQuery = cleanQueryParams(.URL.RawQuery)

		 := &ProxyRequest{
			In:  ,
			Out: ,
		}
		.Rewrite()
		 = .Out
	} else {
		if , ,  := net.SplitHostPort(.RemoteAddr);  == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			,  := .Header["X-Forwarded-For"]
			 :=  &&  == nil // Issue 38079: nil now means don't populate the header
			if len() > 0 {
				 = strings.Join(, ", ") + ", " + 
			}
			if ! {
				.Header.Set("X-Forwarded-For", )
			}
		}
	}

	if ,  := .Header["User-Agent"]; ! {
		// If the outbound request doesn't have a User-Agent header set,
		// don't send the default Go HTTP client User-Agent.
		.Header.Set("User-Agent", "")
	}

	var (
		 sync.Mutex
		  bool
	)
	 := &httptrace.ClientTrace{
		Got1xxResponse: func( int,  textproto.MIMEHeader) error {
			.Lock()
			defer .Unlock()
			if  {
				// If RoundTrip has returned, don't try to further modify
				// the ResponseWriter's header map.
				return nil
			}
			 := .Header()
			copyHeader(, http.Header())
			.WriteHeader()

			// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
			clear()
			return nil
		},
	}
	 = .WithContext(httptrace.WithClientTrace(.Context(), ))

	,  := .RoundTrip()
	.Lock()
	 = true
	.Unlock()
	if  != nil {
		.getErrorHandler()(, , )
		return
	}

	// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
	if .StatusCode == http.StatusSwitchingProtocols {
		if !.modifyResponse(, , ) {
			return
		}
		.handleUpgradeResponse(, , )
		return
	}

	removeHopByHopHeaders(.Header)

	if !.modifyResponse(, , ) {
		return
	}

	copyHeader(.Header(), .Header)

	// The "Trailer" header isn't included in the Transport's response,
	// at least for *http.Transport. Build it up from Trailer.
	 := len(.Trailer)
	if  > 0 {
		 := make([]string, 0, len(.Trailer))
		for  := range .Trailer {
			 = append(, )
		}
		.Header().Add("Trailer", strings.Join(, ", "))
	}

	.WriteHeader(.StatusCode)

	 = .copyResponse(, .Body, .flushInterval())
	if  != nil {
		defer .Body.Close()
		// Since we're streaming the response, if we run into an error all we can do
		// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
		// on read error while copying body.
		if !shouldPanicOnCopyError() {
			.logf("suppressing panic for copyResponse error in test; copy error: %v", )
			return
		}
		panic(http.ErrAbortHandler)
	}
	.Body.Close() // close now, instead of defer, to populate res.Trailer

	if len(.Trailer) > 0 {
		// Force chunking if we saw a response trailer.
		// This prevents net/http from calculating the length for short
		// bodies and adding a Content-Length.
		http.NewResponseController().Flush()
	}

	if len(.Trailer) ==  {
		copyHeader(.Header(), .Trailer)
		return
	}

	for ,  := range .Trailer {
		 = http.TrailerPrefix + 
		for ,  := range  {
			.Header().Add(, )
		}
	}
}

var inOurTests bool // whether we're in our own tests

// shouldPanicOnCopyError reports whether the reverse proxy should
// panic with http.ErrAbortHandler. This is the right thing to do by
// default, but Go 1.10 and earlier did not, so existing unit tests
// weren't expecting panics. Only panic in our own tests, or when
// running under the HTTP server.
func shouldPanicOnCopyError( *http.Request) bool {
	if inOurTests {
		// Our tests know to handle this panic.
		return true
	}
	if .Context().Value(http.ServerContextKey) != nil {
		// We seem to be running under an HTTP server, so
		// it'll recover the panic.
		return true
	}
	// Otherwise act like Go 1.10 and earlier to not break
	// existing tests.
	return false
}

// removeHopByHopHeaders removes hop-by-hop headers.
func removeHopByHopHeaders( http.Header) {
	// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
	for ,  := range ["Connection"] {
		for ,  := range strings.Split(, ",") {
			if  = textproto.TrimString();  != "" {
				.Del()
			}
		}
	}
	// RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers.
	// This behavior is superseded by the RFC 7230 Connection header, but
	// preserve it for backwards compatibility.
	for ,  := range hopHeaders {
		.Del()
	}
}

// flushInterval returns the p.FlushInterval value, conditionally
// overriding its value for a specific request/response.
func ( *ReverseProxy) ( *http.Response) time.Duration {
	 := .Header.Get("Content-Type")

	// For Server-Sent Events responses, flush immediately.
	// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
	if , ,  := mime.ParseMediaType();  == "text/event-stream" {
		return -1 // negative means immediately
	}

	// We might have the case of streaming for which Content-Length might be unset.
	if .ContentLength == -1 {
		return -1
	}

	return .FlushInterval
}

func ( *ReverseProxy) ( http.ResponseWriter,  io.Reader,  time.Duration) error {
	var  io.Writer = 

	if  != 0 {
		 := &maxLatencyWriter{
			dst:     ,
			flush:   http.NewResponseController().Flush,
			latency: ,
		}
		defer .stop()

		// set up initial timer so headers get flushed even if body writes are delayed
		.flushPending = true
		.t = time.AfterFunc(, .delayedFlush)

		 = 
	}

	var  []byte
	if .BufferPool != nil {
		 = .BufferPool.Get()
		defer .BufferPool.Put()
	}
	,  := .copyBuffer(, , )
	return 
}

// copyBuffer returns any write errors or non-EOF read errors, and the amount
// of bytes written.
func ( *ReverseProxy) ( io.Writer,  io.Reader,  []byte) (int64, error) {
	if len() == 0 {
		 = make([]byte, 32*1024)
	}
	var  int64
	for {
		,  := .Read()
		if  != nil &&  != io.EOF &&  != context.Canceled {
			.logf("httputil: ReverseProxy read error during body copy: %v", )
		}
		if  > 0 {
			,  := .Write([:])
			if  > 0 {
				 += int64()
			}
			if  != nil {
				return , 
			}
			if  !=  {
				return , io.ErrShortWrite
			}
		}
		if  != nil {
			if  == io.EOF {
				 = nil
			}
			return , 
		}
	}
}

func ( *ReverseProxy) ( string,  ...any) {
	if .ErrorLog != nil {
		.ErrorLog.Printf(, ...)
	} else {
		log.Printf(, ...)
	}
}

type maxLatencyWriter struct {
	dst     io.Writer
	flush   func() error
	latency time.Duration // non-zero; negative means to flush immediately

	mu           sync.Mutex // protects t, flushPending, and dst.Flush
	t            *time.Timer
	flushPending bool
}

func ( *maxLatencyWriter) ( []byte) ( int,  error) {
	.mu.Lock()
	defer .mu.Unlock()
	,  = .dst.Write()
	if .latency < 0 {
		.flush()
		return
	}
	if .flushPending {
		return
	}
	if .t == nil {
		.t = time.AfterFunc(.latency, .delayedFlush)
	} else {
		.t.Reset(.latency)
	}
	.flushPending = true
	return
}

func ( *maxLatencyWriter) () {
	.mu.Lock()
	defer .mu.Unlock()
	if !.flushPending { // if stop was called but AfterFunc already started this goroutine
		return
	}
	.flush()
	.flushPending = false
}

func ( *maxLatencyWriter) () {
	.mu.Lock()
	defer .mu.Unlock()
	.flushPending = false
	if .t != nil {
		.t.Stop()
	}
}

func upgradeType( http.Header) string {
	if !httpguts.HeaderValuesContainsToken(["Connection"], "Upgrade") {
		return ""
	}
	return .Get("Upgrade")
}

func ( *ReverseProxy) ( http.ResponseWriter,  *http.Request,  *http.Response) {
	 := upgradeType(.Header)
	 := upgradeType(.Header)
	if !ascii.IsPrint() { // We know reqUpType is ASCII, it's checked by the caller.
		.getErrorHandler()(, , fmt.Errorf("backend tried to switch to invalid protocol %q", ))
	}
	if !ascii.EqualFold(, ) {
		.getErrorHandler()(, , fmt.Errorf("backend tried to switch protocol %q when %q was requested", , ))
		return
	}

	,  := .Body.(io.ReadWriteCloser)
	if ! {
		.getErrorHandler()(, , fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
		return
	}

	 := http.NewResponseController()
	, ,  := .Hijack()
	if errors.Is(, http.ErrNotSupported) {
		.getErrorHandler()(, , fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", ))
		return
	}

	 := make(chan bool)
	go func() {
		// Ensure that the cancellation of a request closes the backend.
		// See issue https://golang.org/issue/35559.
		select {
		case <-.Context().Done():
		case <-:
		}
		.Close()
	}()
	defer close()

	if  != nil {
		.getErrorHandler()(, , fmt.Errorf("Hijack failed on protocol switch: %v", ))
		return
	}
	defer .Close()

	copyHeader(.Header(), .Header)

	.Header = .Header()
	.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
	if  := .Write();  != nil {
		.getErrorHandler()(, , fmt.Errorf("response write: %v", ))
		return
	}
	if  := .Flush();  != nil {
		.getErrorHandler()(, , fmt.Errorf("response flush: %v", ))
		return
	}
	 := make(chan error, 1)
	 := switchProtocolCopier{user: , backend: }
	go .copyToBackend()
	go .copyFromBackend()
	<-
}

// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
	user, backend io.ReadWriter
}

func ( switchProtocolCopier) ( chan<- error) {
	,  := io.Copy(.user, .backend)
	 <- 
}

func ( switchProtocolCopier) ( chan<- error) {
	,  := io.Copy(.backend, .user)
	 <- 
}

func cleanQueryParams( string) string {
	 := func( string) string {
		,  := url.ParseQuery()
		return .Encode()
	}
	for  := 0;  < len(); {
		switch [] {
		case ';':
			return ()
		case '%':
			if +2 >= len() || !ishex([+1]) || !ishex([+2]) {
				return ()
			}
			 += 3
		default:
			++
		}
	}
	return 
}

func ishex( byte) bool {
	switch {
	case '0' <=  &&  <= '9':
		return true
	case 'a' <=  &&  <= 'f':
		return true
	case 'A' <=  &&  <= 'F':
		return true
	}
	return false
}