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