mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 07:39:19 -05:00
110 lines
3.5 KiB
Go
110 lines
3.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"golang.org/x/net/http/httpguts"
|
|
)
|
|
|
|
// relayResponse forwards an unmodified upstream body. Callers still own and
|
|
// close resp.Body, including when a failed transfer aborts the handler. A custom
|
|
// header copier may select or rewrite end-to-end headers; it only sees headers
|
|
// after hop-by-hop fields have been removed.
|
|
func (p *Proxy) relayResponse(w http.ResponseWriter, r *http.Request, resp *http.Response, copyHeaders func(http.Header, http.Header)) {
|
|
blocked := relayHopHeaders(resp.Header)
|
|
headers := resp.Header.Clone()
|
|
for name := range blocked {
|
|
headers.Del(name)
|
|
}
|
|
bodyAllowed := r.Method != http.MethodHead && resp.StatusCode >= http.StatusOK &&
|
|
resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusResetContent && resp.StatusCode != http.StatusNotModified
|
|
if resp.StatusCode < http.StatusOK || resp.StatusCode == http.StatusNoContent {
|
|
headers.Del(headerContentLength)
|
|
} else if resp.StatusCode == http.StatusResetContent {
|
|
headers.Set(headerContentLength, "0")
|
|
}
|
|
if copyHeaders == nil {
|
|
copyHeaders = copyRelayHeaders
|
|
}
|
|
copyHeaders(w.Header(), headers)
|
|
|
|
announced := make(map[string]bool)
|
|
if bodyAllowed {
|
|
for name := range resp.Trailer {
|
|
name = http.CanonicalHeaderKey(name)
|
|
if validRelayTrailer(name, blocked) {
|
|
announced[name] = true
|
|
w.Header().Add("Trailer", name)
|
|
}
|
|
}
|
|
if len(announced) > 0 {
|
|
// HTTP/1 trailers require chunking, not a fixed Content-Length.
|
|
w.Header().Del(headerContentLength)
|
|
}
|
|
}
|
|
w.WriteHeader(resp.StatusCode)
|
|
if !bodyAllowed || resp.Body == nil {
|
|
return
|
|
}
|
|
|
|
written, err := io.Copy(w, resp.Body)
|
|
if err != nil {
|
|
upstreamURL := ""
|
|
if resp.Request != nil && resp.Request.URL != nil {
|
|
upstreamURL = resp.Request.URL.Redacted()
|
|
}
|
|
p.Logger.Warn("upstream response relay failed", "url", upstreamURL,
|
|
"status", resp.StatusCode, "bytes", written, "error", err)
|
|
// Headers are already committed: an error page would turn a truncated
|
|
// download into a seemingly successful response. Let net/http close the
|
|
// HTTP/1 connection or reset the HTTP/2 stream instead.
|
|
panic(http.ErrAbortHandler)
|
|
}
|
|
relayTrailers(w, resp.Trailer, announced, blocked)
|
|
}
|
|
|
|
func copyRelayHeaders(dst, src http.Header) {
|
|
for name, values := range src {
|
|
for _, value := range values {
|
|
dst.Add(name, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func relayHopHeaders(headers http.Header) map[string]bool {
|
|
blocked := map[string]bool{
|
|
"Connection": true, "Proxy-Connection": true, "Keep-Alive": true,
|
|
"Proxy-Authenticate": true, "Proxy-Authorization": true, "Te": true,
|
|
"Trailer": true, "Transfer-Encoding": true, "Upgrade": true,
|
|
}
|
|
for _, value := range headers.Values("Connection") {
|
|
for _, name := range strings.Split(value, ",") {
|
|
if name = strings.TrimSpace(name); name != "" {
|
|
blocked[http.CanonicalHeaderKey(name)] = true
|
|
}
|
|
}
|
|
}
|
|
return blocked
|
|
}
|
|
|
|
func validRelayTrailer(name string, blocked map[string]bool) bool {
|
|
return !blocked[name] && httpguts.ValidHeaderFieldName(name) && httpguts.ValidTrailerHeader(name)
|
|
}
|
|
|
|
func relayTrailers(w http.ResponseWriter, trailers http.Header, announced, blocked map[string]bool) {
|
|
for name, values := range trailers {
|
|
name = http.CanonicalHeaderKey(name)
|
|
if !validRelayTrailer(name, blocked) {
|
|
continue
|
|
}
|
|
if !announced[name] {
|
|
// A trailer discovered only at EOF must not become a regular header
|
|
// or trigger automatic Content-Length on a short buffered response.
|
|
_ = http.NewResponseController(w).Flush()
|
|
name = http.TrailerPrefix + name
|
|
}
|
|
w.Header()[name] = append([]string(nil), values...)
|
|
}
|
|
}
|