github.com/sagernet/quic-go@v0.43.1-beta.1/http3/headers.go (about)

     1  package http3
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/quic-go/qpack"
    12  	"golang.org/x/net/http/httpguts"
    13  )
    14  
    15  type header struct {
    16  	// Pseudo header fields defined in RFC 9114
    17  	Path      string
    18  	Method    string
    19  	Authority string
    20  	Scheme    string
    21  	Status    string
    22  	// for Extended connect
    23  	Protocol string
    24  	// parsed and deduplicated
    25  	ContentLength int64
    26  	// all non-pseudo headers
    27  	Headers http.Header
    28  }
    29  
    30  func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) {
    31  	hdr := header{Headers: make(http.Header, len(headers))}
    32  	var readFirstRegularHeader, readContentLength bool
    33  	var contentLengthStr string
    34  	for _, h := range headers {
    35  		// field names need to be lowercase, see section 4.2 of RFC 9114
    36  		if strings.ToLower(h.Name) != h.Name {
    37  			return header{}, fmt.Errorf("header field is not lower-case: %s", h.Name)
    38  		}
    39  		if !httpguts.ValidHeaderFieldValue(h.Value) {
    40  			return header{}, fmt.Errorf("invalid header field value for %s: %q", h.Name, h.Value)
    41  		}
    42  		if h.IsPseudo() {
    43  			if readFirstRegularHeader {
    44  				// all pseudo headers must appear before regular header fields, see section 4.3 of RFC 9114
    45  				return header{}, fmt.Errorf("received pseudo header %s after a regular header field", h.Name)
    46  			}
    47  			var isResponsePseudoHeader bool // pseudo headers are either valid for requests or for responses
    48  			switch h.Name {
    49  			case ":path":
    50  				hdr.Path = h.Value
    51  			case ":method":
    52  				hdr.Method = h.Value
    53  			case ":authority":
    54  				hdr.Authority = h.Value
    55  			case ":protocol":
    56  				hdr.Protocol = h.Value
    57  			case ":scheme":
    58  				hdr.Scheme = h.Value
    59  			case ":status":
    60  				hdr.Status = h.Value
    61  				isResponsePseudoHeader = true
    62  			default:
    63  				return header{}, fmt.Errorf("unknown pseudo header: %s", h.Name)
    64  			}
    65  			if isRequest && isResponsePseudoHeader {
    66  				return header{}, fmt.Errorf("invalid request pseudo header: %s", h.Name)
    67  			}
    68  			if !isRequest && !isResponsePseudoHeader {
    69  				return header{}, fmt.Errorf("invalid response pseudo header: %s", h.Name)
    70  			}
    71  		} else {
    72  			if !httpguts.ValidHeaderFieldName(h.Name) {
    73  				return header{}, fmt.Errorf("invalid header field name: %q", h.Name)
    74  			}
    75  			readFirstRegularHeader = true
    76  			switch h.Name {
    77  			case "content-length":
    78  				// Ignore duplicate Content-Length headers.
    79  				// Fail if the duplicates differ.
    80  				if !readContentLength {
    81  					readContentLength = true
    82  					contentLengthStr = h.Value
    83  				} else if contentLengthStr != h.Value {
    84  					return header{}, fmt.Errorf("contradicting content lengths (%s and %s)", contentLengthStr, h.Value)
    85  				}
    86  			default:
    87  				hdr.Headers.Add(h.Name, h.Value)
    88  			}
    89  		}
    90  	}
    91  	if len(contentLengthStr) > 0 {
    92  		// use ParseUint instead of ParseInt, so that parsing fails on negative values
    93  		cl, err := strconv.ParseUint(contentLengthStr, 10, 63)
    94  		if err != nil {
    95  			return header{}, fmt.Errorf("invalid content length: %w", err)
    96  		}
    97  		hdr.Headers.Set("Content-Length", contentLengthStr)
    98  		hdr.ContentLength = int64(cl)
    99  	}
   100  	return hdr, nil
   101  }
   102  
   103  func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error) {
   104  	hdr, err := parseHeaders(headerFields, true)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	// concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4
   109  	if len(hdr.Headers["Cookie"]) > 0 {
   110  		hdr.Headers.Set("Cookie", strings.Join(hdr.Headers["Cookie"], "; "))
   111  	}
   112  
   113  	isConnect := hdr.Method == http.MethodConnect
   114  	// Extended CONNECT, see https://datatracker.ietf.org/doc/html/rfc8441#section-4
   115  	isExtendedConnected := isConnect && hdr.Protocol != ""
   116  	if isExtendedConnected {
   117  		if hdr.Scheme == "" || hdr.Path == "" || hdr.Authority == "" {
   118  			return nil, errors.New("extended CONNECT: :scheme, :path and :authority must not be empty")
   119  		}
   120  	} else if isConnect {
   121  		if hdr.Path != "" || hdr.Authority == "" { // normal CONNECT
   122  			return nil, errors.New(":path must be empty and :authority must not be empty")
   123  		}
   124  	} else if len(hdr.Path) == 0 || len(hdr.Authority) == 0 || len(hdr.Method) == 0 {
   125  		return nil, errors.New(":path, :authority and :method must not be empty")
   126  	}
   127  
   128  	if !isExtendedConnected && len(hdr.Protocol) > 0 {
   129  		return nil, errors.New(":protocol must be empty")
   130  	}
   131  
   132  	var u *url.URL
   133  	var requestURI string
   134  
   135  	protocol := "HTTP/3.0"
   136  
   137  	if isConnect {
   138  		u = &url.URL{}
   139  		if isExtendedConnected {
   140  			u, err = url.ParseRequestURI(hdr.Path)
   141  			if err != nil {
   142  				return nil, err
   143  			}
   144  			protocol = hdr.Protocol
   145  		} else {
   146  			u.Path = hdr.Path
   147  		}
   148  		u.Scheme = hdr.Scheme
   149  		u.Host = hdr.Authority
   150  		requestURI = hdr.Authority
   151  	} else {
   152  		u, err = url.ParseRequestURI(hdr.Path)
   153  		if err != nil {
   154  			return nil, fmt.Errorf("invalid content length: %w", err)
   155  		}
   156  		requestURI = hdr.Path
   157  	}
   158  
   159  	return &http.Request{
   160  		Method:        hdr.Method,
   161  		URL:           u,
   162  		Proto:         protocol,
   163  		ProtoMajor:    3,
   164  		ProtoMinor:    0,
   165  		Header:        hdr.Headers,
   166  		Body:          nil,
   167  		ContentLength: hdr.ContentLength,
   168  		Host:          hdr.Authority,
   169  		RequestURI:    requestURI,
   170  	}, nil
   171  }
   172  
   173  func hostnameFromURL(url *url.URL) string {
   174  	if url != nil {
   175  		return url.Host
   176  	}
   177  	return ""
   178  }
   179  
   180  func responseFromHeaders(headerFields []qpack.HeaderField) (*http.Response, error) {
   181  	hdr, err := parseHeaders(headerFields, false)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	if hdr.Status == "" {
   186  		return nil, errors.New("missing status field")
   187  	}
   188  	rsp := &http.Response{
   189  		Proto:         "HTTP/3.0",
   190  		ProtoMajor:    3,
   191  		Header:        hdr.Headers,
   192  		ContentLength: hdr.ContentLength,
   193  	}
   194  	status, err := strconv.Atoi(hdr.Status)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("invalid status code: %w", err)
   197  	}
   198  	rsp.StatusCode = status
   199  	rsp.Status = hdr.Status + " " + http.StatusText(status)
   200  	return rsp, nil
   201  }