1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http3;
17
18 import io.netty.handler.codec.http.HttpHeaderNames;
19 import io.netty.handler.codec.http.HttpMethod;
20
21 import java.util.function.BiConsumer;
22
23 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.AUTHORITY;
24 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.METHOD;
25 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.PATH;
26 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.PROTOCOL;
27 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.SCHEME;
28 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.STATUS;
29 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.getPseudoHeader;
30 import static io.netty.handler.codec.http3.Http3Headers.PseudoHeaderName.hasPseudoHeaderFormat;
31
32
33
34
35
36 final class Http3HeadersSink implements BiConsumer<CharSequence, CharSequence> {
37 private final Http3Headers headers;
38 private final long maxHeaderListSize;
39 private final boolean validate;
40 private final boolean trailer;
41 private long headersLength;
42 private boolean exceededMaxLength;
43 private Http3HeadersValidationException validationException;
44 private HeaderType previousType;
45 private boolean request;
46 private int receivedPseudoHeaders;
47
48 Http3HeadersSink(Http3Headers headers, long maxHeaderListSize, boolean validate, boolean trailer) {
49 this.headers = headers;
50 this.maxHeaderListSize = maxHeaderListSize;
51 this.validate = validate;
52 this.trailer = trailer;
53 }
54
55
56
57
58 void finish() throws Http3HeadersValidationException, Http3Exception {
59 if (exceededMaxLength) {
60 throw new Http3Exception(Http3ErrorCode.H3_EXCESSIVE_LOAD,
61 String.format("Header size exceeded max allowed size (%d)", maxHeaderListSize));
62 }
63 if (validationException != null) {
64 throw validationException;
65 }
66 if (validate) {
67 if (trailer) {
68 if (receivedPseudoHeaders != 0) {
69
70 throw new Http3HeadersValidationException("Pseudo-header(s) included in trailers.");
71 }
72 return;
73 }
74
75
76 if (request) {
77 CharSequence method = headers.method();
78
79 if (HttpMethod.CONNECT.asciiName().contentEqualsIgnoreCase(method)) {
80
81
82 if ((receivedPseudoHeaders & PROTOCOL.getFlag()) != 0) {
83
84
85
86
87
88
89 final int requiredPseudoHeaders = METHOD.getFlag() | SCHEME.getFlag() |
90 AUTHORITY.getFlag() | PATH.getFlag() | PROTOCOL.getFlag();
91 if (receivedPseudoHeaders != requiredPseudoHeaders) {
92 throw new Http3HeadersValidationException(
93 "Not all mandatory pseudo-headers included for Extended CONNECT.");
94 }
95 } else {
96
97
98
99 final int requiredPseudoHeaders = METHOD.getFlag() | AUTHORITY.getFlag();
100 if (receivedPseudoHeaders != requiredPseudoHeaders) {
101 throw new Http3HeadersValidationException("Not all mandatory pseudo-headers included.");
102 }
103 }
104 } else if (HttpMethod.OPTIONS.asciiName().contentEqualsIgnoreCase(method)) {
105
106
107
108
109
110
111
112
113 final int requiredPseudoHeaders = METHOD.getFlag() | SCHEME.getFlag() | PATH.getFlag();
114 if ((receivedPseudoHeaders & requiredPseudoHeaders) != requiredPseudoHeaders ||
115 (!authorityOrHostHeaderReceived() && !"*".contentEquals(headers.path()))) {
116 throw new Http3HeadersValidationException("Not all mandatory pseudo-headers included.");
117 }
118 } else {
119
120
121
122
123
124 final int requiredPseudoHeaders = METHOD.getFlag() | SCHEME.getFlag() | PATH.getFlag();
125 if ((receivedPseudoHeaders & requiredPseudoHeaders) != requiredPseudoHeaders ||
126 !authorityOrHostHeaderReceived()) {
127 throw new Http3HeadersValidationException("Not all mandatory pseudo-headers included.");
128 }
129 }
130 } else {
131
132
133 if (receivedPseudoHeaders != STATUS.getFlag()) {
134 throw new Http3HeadersValidationException("Not all mandatory pseudo-headers included.");
135 }
136 }
137 }
138 }
139
140
141
142
143
144
145 private boolean authorityOrHostHeaderReceived() {
146 return (receivedPseudoHeaders & AUTHORITY.getFlag()) == AUTHORITY.getFlag() ||
147 headers.contains(HttpHeaderNames.HOST);
148 }
149
150 @Override
151 public void accept(CharSequence name, CharSequence value) {
152 headersLength += QpackHeaderField.sizeOf(name, value);
153 exceededMaxLength |= headersLength > maxHeaderListSize;
154
155 if (exceededMaxLength || validationException != null) {
156
157 return;
158 }
159
160 if (validate) {
161 try {
162 validate(headers, name);
163 } catch (Http3HeadersValidationException ex) {
164 validationException = ex;
165 return;
166 }
167 }
168
169 headers.add(name, value);
170 }
171
172 private void validate(Http3Headers headers, CharSequence name) {
173 if (hasPseudoHeaderFormat(name)) {
174 if (previousType == HeaderType.REGULAR_HEADER) {
175 throw new Http3HeadersValidationException(
176 String.format("Pseudo-header field '%s' found after regular header.", name));
177 }
178
179 final Http3Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name);
180 if (pseudoHeader == null) {
181 throw new Http3HeadersValidationException(
182 String.format("Invalid HTTP/3 pseudo-header '%s' encountered.", name));
183 }
184 if ((receivedPseudoHeaders & pseudoHeader.getFlag()) != 0) {
185
186 throw new Http3HeadersValidationException(
187 String.format("Pseudo-header field '%s' exists already.", name));
188 }
189 receivedPseudoHeaders |= pseudoHeader.getFlag();
190
191 final HeaderType currentHeaderType = pseudoHeader.isRequestOnly() ?
192 HeaderType.REQUEST_PSEUDO_HEADER : HeaderType.RESPONSE_PSEUDO_HEADER;
193 request = pseudoHeader.isRequestOnly();
194 previousType = currentHeaderType;
195 } else {
196 previousType = HeaderType.REGULAR_HEADER;
197 }
198 }
199
200 private enum HeaderType {
201 REGULAR_HEADER,
202 REQUEST_PSEUDO_HEADER,
203 RESPONSE_PSEUDO_HEADER
204 }
205 }