View Javadoc
1   /*
2    * Copyright 2015 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  
17  /*
18   * Copyright 2014 Twitter, Inc.
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *     https://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package io.netty5.handler.codec.http2;
33  
34  import io.netty5.handler.codec.UnsupportedValueConverter;
35  import io.netty5.util.AsciiString;
36  
37  import java.util.Arrays;
38  import java.util.List;
39  
40  import static io.netty5.handler.codec.http2.HpackUtil.equalsVariableTime;
41  
42  final class HpackStaticTable {
43  
44      static final int NOT_FOUND = -1;
45  
46      // Appendix A: Static Table
47      // https://tools.ietf.org/html/rfc7541#appendix-A
48      private static final List<HpackHeaderField> STATIC_TABLE = Arrays.asList(
49      /*  1 */ newEmptyHeaderField(":authority"),
50      /*  2 */ newHeaderField(":method", "GET"),
51      /*  3 */ newHeaderField(":method", "POST"),
52      /*  4 */ newHeaderField(":path", "/"),
53      /*  5 */ newHeaderField(":path", "/index.html"),
54      /*  6 */ newHeaderField(":scheme", "http"),
55      /*  7 */ newHeaderField(":scheme", "https"),
56      /*  8 */ newHeaderField(":status", "200"),
57      /*  9 */ newHeaderField(":status", "204"),
58      /* 10 */ newHeaderField(":status", "206"),
59      /* 11 */ newHeaderField(":status", "304"),
60      /* 12 */ newHeaderField(":status", "400"),
61      /* 13 */ newHeaderField(":status", "404"),
62      /* 14 */ newHeaderField(":status", "500"),
63      /* 15 */ newEmptyHeaderField("accept-charset"),
64      /* 16 */ newHeaderField("accept-encoding", "gzip, deflate"),
65      /* 17 */ newEmptyHeaderField("accept-language"),
66      /* 18 */ newEmptyHeaderField("accept-ranges"),
67      /* 19 */ newEmptyHeaderField("accept"),
68      /* 20 */ newEmptyHeaderField("access-control-allow-origin"),
69      /* 21 */ newEmptyHeaderField("age"),
70      /* 22 */ newEmptyHeaderField("allow"),
71      /* 23 */ newEmptyHeaderField("authorization"),
72      /* 24 */ newEmptyHeaderField("cache-control"),
73      /* 25 */ newEmptyHeaderField("content-disposition"),
74      /* 26 */ newEmptyHeaderField("content-encoding"),
75      /* 27 */ newEmptyHeaderField("content-language"),
76      /* 28 */ newEmptyHeaderField("content-length"),
77      /* 29 */ newEmptyHeaderField("content-location"),
78      /* 30 */ newEmptyHeaderField("content-range"),
79      /* 31 */ newEmptyHeaderField("content-type"),
80      /* 32 */ newEmptyHeaderField("cookie"),
81      /* 33 */ newEmptyHeaderField("date"),
82      /* 34 */ newEmptyHeaderField("etag"),
83      /* 35 */ newEmptyHeaderField("expect"),
84      /* 36 */ newEmptyHeaderField("expires"),
85      /* 37 */ newEmptyHeaderField("from"),
86      /* 38 */ newEmptyHeaderField("host"),
87      /* 39 */ newEmptyHeaderField("if-match"),
88      /* 40 */ newEmptyHeaderField("if-modified-since"),
89      /* 41 */ newEmptyHeaderField("if-none-match"),
90      /* 42 */ newEmptyHeaderField("if-range"),
91      /* 43 */ newEmptyHeaderField("if-unmodified-since"),
92      /* 44 */ newEmptyHeaderField("last-modified"),
93      /* 45 */ newEmptyHeaderField("link"),
94      /* 46 */ newEmptyHeaderField("location"),
95      /* 47 */ newEmptyHeaderField("max-forwards"),
96      /* 48 */ newEmptyHeaderField("proxy-authenticate"),
97      /* 49 */ newEmptyHeaderField("proxy-authorization"),
98      /* 50 */ newEmptyHeaderField("range"),
99      /* 51 */ newEmptyHeaderField("referer"),
100     /* 52 */ newEmptyHeaderField("refresh"),
101     /* 53 */ newEmptyHeaderField("retry-after"),
102     /* 54 */ newEmptyHeaderField("server"),
103     /* 55 */ newEmptyHeaderField("set-cookie"),
104     /* 56 */ newEmptyHeaderField("strict-transport-security"),
105     /* 57 */ newEmptyHeaderField("transfer-encoding"),
106     /* 58 */ newEmptyHeaderField("user-agent"),
107     /* 59 */ newEmptyHeaderField("vary"),
108     /* 60 */ newEmptyHeaderField("via"),
109     /* 61 */ newEmptyHeaderField("www-authenticate")
110     );
111 
112     private static HpackHeaderField newEmptyHeaderField(String name) {
113         return new HpackHeaderField(AsciiString.cached(name), AsciiString.EMPTY_STRING);
114     }
115 
116     private static HpackHeaderField newHeaderField(String name, String value) {
117         return new HpackHeaderField(AsciiString.cached(name), AsciiString.cached(value));
118     }
119 
120     private static final CharSequenceMap<Integer> STATIC_INDEX_BY_NAME = createMap();
121 
122     private static final int MAX_SAME_NAME_FIELD_INDEX = maxSameNameFieldIndex();
123 
124     /**
125      * The number of header fields in the static table.
126      */
127     static final int length = STATIC_TABLE.size();
128 
129     /**
130      * Return the header field at the given index value.
131      */
132     static HpackHeaderField getEntry(int index) {
133         return STATIC_TABLE.get(index - 1);
134     }
135 
136     /**
137      * Returns the lowest index value for the given header field name in the static table. Returns
138      * -1 if the header field name is not in the static table.
139      */
140     static int getIndex(CharSequence name) {
141         Integer index = STATIC_INDEX_BY_NAME.get(name);
142         if (index == null) {
143             return NOT_FOUND;
144         }
145         return index;
146     }
147 
148     /**
149      * Returns the index value for the given header field in the static table. Returns -1 if the
150      * header field is not in the static table.
151      */
152     static int getIndexInsensitive(CharSequence name, CharSequence value) {
153         int index = getIndex(name);
154         if (index == NOT_FOUND) {
155             return NOT_FOUND;
156         }
157 
158         // Compare values for the first name match
159         HpackHeaderField entry = getEntry(index);
160         if (equalsVariableTime(value, entry.value)) {
161             return index;
162         }
163 
164         // Note this assumes all entries for a given header field are sequential.
165         index++;
166         while (index <= MAX_SAME_NAME_FIELD_INDEX) {
167             entry = getEntry(index);
168             if (!equalsVariableTime(name, entry.name)) {
169                 // As far as fields with the same name are placed in the table sequentially
170                 // and INDEX_BY_NAME returns index of the fist position, - it's safe to
171                 // exit immediately.
172                 return NOT_FOUND;
173             }
174             if (equalsVariableTime(value, entry.value)) {
175                 return index;
176             }
177             index++;
178         }
179 
180         return NOT_FOUND;
181     }
182 
183     // create a map CharSequenceMap header name to index value to allow quick lookup
184     private static CharSequenceMap<Integer> createMap() {
185         int length = STATIC_TABLE.size();
186         CharSequenceMap<Integer> ret = new CharSequenceMap<>(true,
187                 UnsupportedValueConverter.instance(), length);
188         // Iterate through the static table in reverse order to
189         // save the smallest index for a given name in the map.
190         for (int index = length; index > 0; index--) {
191             HpackHeaderField entry = getEntry(index);
192             CharSequence name = entry.name;
193             ret.set(name, index);
194         }
195         return ret;
196     }
197 
198     /**
199      * Returns the last position in the array that contains multiple
200      * fields with the same name. Starting from this position, all
201      * names are unique. Similar to {@link #getIndexInsensitive(CharSequence, CharSequence)} method
202      * assumes all entries for a given header field are sequential
203      */
204     private static int maxSameNameFieldIndex() {
205         final int length = STATIC_TABLE.size();
206         HpackHeaderField cursor = getEntry(length);
207         for (int index = length - 1; index > 0; index--) {
208             HpackHeaderField entry = getEntry(index);
209             if (equalsVariableTime(entry.name, cursor.name)) {
210                 return index + 1;
211             } else {
212                 cursor = entry;
213             }
214         }
215         return length;
216     }
217 
218     // singleton
219     private HpackStaticTable() {
220     }
221 }