View Javadoc
1   /*
2    * Copyright 2020 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http3;
17  
18  import io.netty.handler.codec.UnsupportedValueConverter;
19  import io.netty.util.AsciiString;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  final class QpackStaticTable {
26  
27      static final int NOT_FOUND = -1;
28  
29      /**
30       * Special mask used to disambiguate exact pair index from
31       * name only index and avoid executing lookup twice. Supposed
32       * to be used internally. The value should be large enough
33       * not to override bits from static table index (current size
34       * of the table is 99 elements).
35       */
36      static final int MASK_NAME_REF = 1 << 10;
37  
38      /**
39       * <a href="https://www.rfc-editor.org/rfc/rfc9204.html#name-static-table-2>Appendix A: Static Table</a>
40       */
41      private static final List<QpackHeaderField> STATIC_TABLE = Arrays.asList(
42          newEmptyHeaderField(":authority"),
43          newHeaderField(":path", "/"),
44          newHeaderField("age", "0"),
45          newEmptyHeaderField("content-disposition"),
46          newHeaderField("content-length", "0"),
47          newEmptyHeaderField("cookie"),
48          newEmptyHeaderField("date"),
49          newEmptyHeaderField("etag"),
50          newEmptyHeaderField("if-modified-since"),
51          newEmptyHeaderField("if-none-match"),
52          newEmptyHeaderField("last-modified"),
53          newEmptyHeaderField("link"),
54          newEmptyHeaderField("location"),
55          newEmptyHeaderField("referer"),
56          newEmptyHeaderField("set-cookie"),
57          newHeaderField(":method", "CONNECT"),
58          newHeaderField(":method", "DELETE"),
59          newHeaderField(":method", "GET"),
60          newHeaderField(":method", "HEAD"),
61          newHeaderField(":method", "OPTIONS"),
62          newHeaderField(":method", "POST"),
63          newHeaderField(":method", "PUT"),
64          newHeaderField(":scheme", "http"),
65          newHeaderField(":scheme", "https"),
66          newHeaderField(":status", "103"),
67          newHeaderField(":status", "200"),
68          newHeaderField(":status", "304"),
69          newHeaderField(":status", "404"),
70          newHeaderField(":status", "503"),
71          newHeaderField("accept", "*/*"),
72          newHeaderField("accept", "application/dns-message"),
73          newHeaderField("accept-encoding", "gzip, deflate, br"),
74          newHeaderField("accept-ranges", "bytes"),
75          newHeaderField("access-control-allow-headers", "cache-control"),
76          newHeaderField("access-control-allow-headers", "content-type"),
77          newHeaderField("access-control-allow-origin", "*"),
78          newHeaderField("cache-control", "max-age=0"),
79          newHeaderField("cache-control", "max-age=2592000"),
80          newHeaderField("cache-control", "max-age=604800"),
81          newHeaderField("cache-control", "no-cache"),
82          newHeaderField("cache-control", "no-store"),
83          newHeaderField("cache-control", "public, max-age=31536000"),
84          newHeaderField("content-encoding", "br"),
85          newHeaderField("content-encoding", "gzip"),
86          newHeaderField("content-type", "application/dns-message"),
87          newHeaderField("content-type", "application/javascript"),
88          newHeaderField("content-type", "application/json"),
89          newHeaderField("content-type", "application/x-www-form-urlencoded"),
90          newHeaderField("content-type", "image/gif"),
91          newHeaderField("content-type", "image/jpeg"),
92          newHeaderField("content-type", "image/png"),
93          newHeaderField("content-type", "text/css"),
94          newHeaderField("content-type", "text/html;charset=utf-8"),
95          newHeaderField("content-type", "text/plain"),
96          newHeaderField("content-type", "text/plain;charset=utf-8"),
97          newHeaderField("range", "bytes=0-"),
98          newHeaderField("strict-transport-security", "max-age=31536000"),
99          newHeaderField("strict-transport-security", "max-age=31536000;includesubdomains"),
100         newHeaderField("strict-transport-security", "max-age=31536000;includesubdomains;preload"),
101         newHeaderField("vary", "accept-encoding"),
102         newHeaderField("vary", "origin"),
103         newHeaderField("x-content-type-options", "nosniff"),
104         newHeaderField("x-xss-protection", "1; mode=block"),
105         newHeaderField(":status", "100"),
106         newHeaderField(":status", "204"),
107         newHeaderField(":status", "206"),
108         newHeaderField(":status", "302"),
109         newHeaderField(":status", "400"),
110         newHeaderField(":status", "403"),
111         newHeaderField(":status", "421"),
112         newHeaderField(":status", "425"),
113         newHeaderField(":status", "500"),
114         newEmptyHeaderField("accept-language"),
115         newHeaderField("access-control-allow-credentials", "FALSE"),
116         newHeaderField("access-control-allow-credentials", "TRUE"),
117         newHeaderField("access-control-allow-headers", "*"),
118         newHeaderField("access-control-allow-methods", "get"),
119         newHeaderField("access-control-allow-methods", "get, post, options"),
120         newHeaderField("access-control-allow-methods", "options"),
121         newHeaderField("access-control-expose-headers", "content-length"),
122         newHeaderField("access-control-request-headers", "content-type"),
123         newHeaderField("access-control-request-method", "get"),
124         newHeaderField("access-control-request-method", "post"),
125         newHeaderField("alt-svc", "clear"),
126         newEmptyHeaderField("authorization"),
127         newHeaderField("content-security-policy", "script-src 'none';object-src 'none';base-uri 'none'"),
128         newHeaderField("early-data", "1"),
129         newEmptyHeaderField("expect-ct"),
130         newEmptyHeaderField("forwarded"),
131         newEmptyHeaderField("if-range"),
132         newEmptyHeaderField("origin"),
133         newHeaderField("purpose", "prefetch"),
134         newEmptyHeaderField("server"),
135         newHeaderField("timing-allow-origin", "*"),
136         newHeaderField("upgrade-insecure-requests", "1"),
137         newEmptyHeaderField("user-agent"),
138         newEmptyHeaderField("x-forwarded-for"),
139         newHeaderField("x-frame-options", "deny"),
140         newHeaderField("x-frame-options", "sameorigin"));
141 
142     /**
143      * The number of header fields in the static table.
144      */
145     static final int length = STATIC_TABLE.size();
146 
147     private static final CharSequenceMap<List<Integer>> STATIC_INDEX_BY_NAME = createMap(length);
148 
149     private static QpackHeaderField newEmptyHeaderField(String name) {
150         return new QpackHeaderField(AsciiString.cached(name), AsciiString.EMPTY_STRING);
151     }
152 
153     private static QpackHeaderField newHeaderField(String name, String value) {
154         return new QpackHeaderField(AsciiString.cached(name), AsciiString.cached(value));
155     }
156 
157     /**
158      * Return the header field at the given index value.
159      * Note that QPACK uses 0-based indexing when HPACK is using 1-based.
160      */
161     static QpackHeaderField getField(int index) {
162         return STATIC_TABLE.get(index);
163     }
164 
165     /**
166      * Returns the lowest index value for the given header field name in the static
167      * table. Returns -1 if the header field name is not in the static table.
168      */
169     static int getIndex(CharSequence name) {
170         List<Integer> index = STATIC_INDEX_BY_NAME.get(name);
171         if (index == null) {
172             return NOT_FOUND;
173         }
174 
175         return index.get(0);
176     }
177 
178     /**
179      * Returns:
180      *    a) the index value for the given header field in the static table (when found);
181      *    b) the index value for a given name with a single bit masked (no exact match);
182      *    c) -1 if name was not found in the static table.
183      */
184     static int findFieldIndex(CharSequence name, CharSequence value) {
185         final List<Integer> nameIndex = STATIC_INDEX_BY_NAME.get(name);
186 
187         // Early return if name not found in the table.
188         if (nameIndex == null) {
189             return NOT_FOUND;
190         }
191 
192         // If name was found, check all subsequence elements of the table for exact match.
193         for (int index: nameIndex) {
194             QpackHeaderField field = STATIC_TABLE.get(index);
195             if (QpackUtil.equalsVariableTime(value, field.value)) {
196                 return index;
197             }
198         }
199 
200         // No exact match was found but we still can reference the name.
201         return nameIndex.get(0) | MASK_NAME_REF;
202     }
203 
204     /**
205      * Creates a map CharSequenceMap header name to index value to allow quick lookup.
206      */
207     @SuppressWarnings("unchecked")
208     private static CharSequenceMap<List<Integer>> createMap(int length) {
209         CharSequenceMap<List<Integer>> mapping =
210             new CharSequenceMap<List<Integer>>(true, UnsupportedValueConverter.<List<Integer>>instance(), length);
211         for (int index = 0; index < length; index++) {
212             final QpackHeaderField field = getField(index);
213             final List<Integer> cursor = mapping.get(field.name);
214             if (cursor == null) {
215                 final List<Integer> holder = new ArrayList<>(16);
216                 holder.add(index);
217                 mapping.set(field.name, holder);
218             } else {
219                 cursor.add(index);
220             }
221         }
222         return mapping;
223     }
224 
225     private QpackStaticTable() {
226     }
227 }