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