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