1515 */
1616package io .netty .incubator .codec .http3 ;
1717
18+ import io .netty .handler .codec .http .HttpHeaderNames ;
1819import io .netty .handler .codec .http .HttpMethod ;
1920
2021import java .util .function .BiConsumer ;
2122
23+ import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .AUTHORITY ;
24+ import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .METHOD ;
25+ import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .PATH ;
26+ import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .SCHEME ;
27+ import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .STATUS ;
2228import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .getPseudoHeader ;
2329import static io .netty .incubator .codec .http3 .Http3Headers .PseudoHeaderName .hasPseudoHeaderFormat ;
2430
@@ -36,7 +42,7 @@ final class Http3HeadersSink implements BiConsumer<CharSequence, CharSequence> {
3642 private Http3HeadersValidationException validationException ;
3743 private HeaderType previousType ;
3844 private boolean request ;
39- private int pseudoHeadersCount ;
45+ private int receivedPseudoHeaders ;
4046
4147 Http3HeadersSink (Http3Headers headers , long maxHeaderListSize , boolean validate , boolean trailer ) {
4248 this .headers = headers ;
@@ -58,7 +64,7 @@ void finish() throws Http3HeadersValidationException, Http3Exception {
5864 }
5965 if (validate ) {
6066 if (trailer ) {
61- if (pseudoHeadersCount != 0 ) {
67+ if (receivedPseudoHeaders != 0 ) {
6268 // Trailers must not have pseudo headers.
6369 throw new Http3HeadersValidationException ("Pseudo-header(s) included in trailers." );
6470 }
@@ -69,16 +75,12 @@ void finish() throws Http3HeadersValidationException, Http3Exception {
6975 if (request ) {
7076 CharSequence method = headers .method ();
7177 // fast-path
72- if (pseudoHeadersCount < 2 ) {
73- // There can't be any duplicates for pseudy header names.
74- throw new Http3HeadersValidationException ("Not all mandatory pseudo-headers included." );
75- }
7678 if (HttpMethod .CONNECT .asciiName ().contentEqualsIgnoreCase (method )) {
7779 // For CONNECT we must only include:
7880 // - :method
7981 // - :authority
80- if ( pseudoHeadersCount != 2 || headers . authority () == null ) {
81- // There can't be any duplicates for pseudy header names.
82+ final int requiredPseudoHeaders = METHOD . getFlag () | AUTHORITY . getFlag ();
83+ if ( receivedPseudoHeaders != requiredPseudoHeaders ) {
8284 throw new Http3HeadersValidationException ("Not all mandatory pseudo-headers included." );
8385 }
8486 } else if (HttpMethod .OPTIONS .asciiName ().contentEqualsIgnoreCase (method )) {
@@ -90,36 +92,43 @@ void finish() throws Http3HeadersValidationException, Http3Exception {
9092 // - :scheme
9193 // - :authority
9294 // - :path
93- if (pseudoHeadersCount != 4 &&
94- // - :method
95- // - :scheme
96- // - :path
97- !(pseudoHeadersCount == 3 && headers .authority () == null &&
98- "*" .contentEquals (headers .path ()))) {
95+ final int requiredPseudoHeaders = METHOD .getFlag () | SCHEME .getFlag () | PATH .getFlag ();
96+ if ((receivedPseudoHeaders & requiredPseudoHeaders ) != requiredPseudoHeaders ||
97+ (!authorityOrHostHeaderReceived () && !"*" .contentEquals (headers .path ()))) {
9998 throw new Http3HeadersValidationException ("Not all mandatory pseudo-headers included." );
10099 }
101100 } else {
102- // For requests we must include:
101+ // For other requests we must include:
103102 // - :method
104103 // - :scheme
105104 // - :authority
106105 // - :path
107- if (pseudoHeadersCount != 4 ) {
108- // There can't be any duplicates for pseudy header names.
106+ final int requiredPseudoHeaders = METHOD .getFlag () | SCHEME .getFlag () | PATH .getFlag ();
107+ if ((receivedPseudoHeaders & requiredPseudoHeaders ) != requiredPseudoHeaders ||
108+ !authorityOrHostHeaderReceived ()) {
109109 throw new Http3HeadersValidationException ("Not all mandatory pseudo-headers included." );
110110 }
111111 }
112112 } else {
113113 // For responses we must include:
114114 // - :status
115- if (pseudoHeadersCount != 1 ) {
116- // There can't be any duplicates for pseudy header names.
115+ if (receivedPseudoHeaders != STATUS .getFlag ()) {
117116 throw new Http3HeadersValidationException ("Not all mandatory pseudo-headers included." );
118117 }
119118 }
120119 }
121120 }
122121
122+ /**
123+ * Find host header field in case the :authority pseudo header is not specified.
124+ * See:
125+ * https://www.rfc-editor.org/rfc/rfc9110#section-7.2
126+ */
127+ private boolean authorityOrHostHeaderReceived () {
128+ return (receivedPseudoHeaders & AUTHORITY .getFlag ()) == AUTHORITY .getFlag () ||
129+ headers .contains (HttpHeaderNames .HOST );
130+ }
131+
123132 @ Override
124133 public void accept (CharSequence name , CharSequence value ) {
125134 headersLength += QpackHeaderField .sizeOf (name , value );
@@ -154,19 +163,15 @@ private void validate(Http3Headers headers, CharSequence name) {
154163 throw new Http3HeadersValidationException (
155164 String .format ("Invalid HTTP/3 pseudo-header '%s' encountered." , name ));
156165 }
157-
158- final HeaderType currentHeaderType = pseudoHeader .isRequestOnly () ?
159- HeaderType .REQUEST_PSEUDO_HEADER : HeaderType .RESPONSE_PSEUDO_HEADER ;
160- if (previousType != null && currentHeaderType != previousType ) {
161- throw new Http3HeadersValidationException ("Mix of request and response pseudo-headers." );
162- }
163-
164- if (headers .contains (name )) {
166+ if ((receivedPseudoHeaders & pseudoHeader .getFlag ()) != 0 ) {
165167 // There can't be any duplicates for pseudy header names.
166168 throw new Http3HeadersValidationException (
167169 String .format ("Pseudo-header field '%s' exists already." , name ));
168170 }
169- pseudoHeadersCount ++;
171+ receivedPseudoHeaders |= pseudoHeader .getFlag ();
172+
173+ final HeaderType currentHeaderType = pseudoHeader .isRequestOnly () ?
174+ HeaderType .REQUEST_PSEUDO_HEADER : HeaderType .RESPONSE_PSEUDO_HEADER ;
170175 request = pseudoHeader .isRequestOnly ();
171176 previousType = currentHeaderType ;
172177 } else {
0 commit comments