View Javadoc
1   /*
2    * Copyright 2017 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.http;
17  
18  import io.netty.util.AsciiString;
19  import io.netty.util.internal.UnstableApi;
20  
21  import java.util.AbstractMap.SimpleImmutableEntry;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.NoSuchElementException;
29  import java.util.Set;
30  
31  import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
32  import static io.netty.util.AsciiString.contentEquals;
33  import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
34  
35  /**
36   * A variant of {@link HttpHeaders} which only supports read-only methods.
37   * <p>
38   * Any array passed to this class may be used directly in the underlying data structures of this class. If these
39   * arrays may be modified it is the caller's responsibility to supply this class with a copy of the array.
40   * <p>
41   * This may be a good alternative to {@link DefaultHttpHeaders} if your have a fixed set of headers which will not
42   * change.
43   */
44  @UnstableApi
45  public final class ReadOnlyHttpHeaders extends HttpHeaders {
46      private final CharSequence[] nameValuePairs;
47  
48      /**
49       * Create a new instance.
50       * @param validateHeaders {@code true} to validate the contents of each header name.
51       * @param nameValuePairs An array of the structure {@code [<name,value>,<name,value>,...]}.
52       *                      A copy will <strong>NOT</strong> be made of this array. If the contents of this array
53       *                      may be modified externally you are responsible for passing in a copy.
54       */
55      public ReadOnlyHttpHeaders(boolean validateHeaders, CharSequence... nameValuePairs) {
56          if ((nameValuePairs.length & 1) != 0) {
57              throw newInvalidArraySizeException();
58          }
59          if (validateHeaders) {
60              validateHeaders(nameValuePairs);
61          }
62          this.nameValuePairs = nameValuePairs;
63      }
64  
65      private static IllegalArgumentException newInvalidArraySizeException() {
66          return new IllegalArgumentException("nameValuePairs must be arrays of [name, value] pairs");
67      }
68  
69      private static void validateHeaders(CharSequence... keyValuePairs) {
70          for (int i = 0; i < keyValuePairs.length; i += 2) {
71              DefaultHttpHeadersFactory.headersFactory().getNameValidator().validateName(keyValuePairs[i]);
72          }
73      }
74  
75      private CharSequence get0(CharSequence name) {
76          final int nameHash = AsciiString.hashCode(name);
77          for (int i = 0; i < nameValuePairs.length; i += 2) {
78              CharSequence roName = nameValuePairs[i];
79              if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
80                  // Suppress a warning out of bounds access since the constructor allows only pairs
81                  return nameValuePairs[i + 1];
82              }
83          }
84          return null;
85      }
86  
87      @Override
88      public String get(String name) {
89          CharSequence value = get0(name);
90          return value == null ? null : value.toString();
91      }
92  
93      @Override
94      public Integer getInt(CharSequence name) {
95          CharSequence value = get0(name);
96          return value == null ? null : INSTANCE.convertToInt(value);
97      }
98  
99      @Override
100     public int getInt(CharSequence name, int defaultValue) {
101         CharSequence value = get0(name);
102         return value == null ? defaultValue : INSTANCE.convertToInt(value);
103     }
104 
105     @Override
106     public Short getShort(CharSequence name) {
107         CharSequence value = get0(name);
108         return value == null ? null : INSTANCE.convertToShort(value);
109     }
110 
111     @Override
112     public short getShort(CharSequence name, short defaultValue) {
113         CharSequence value = get0(name);
114         return value == null ? defaultValue : INSTANCE.convertToShort(value);
115     }
116 
117     @Override
118     public Long getTimeMillis(CharSequence name) {
119         CharSequence value = get0(name);
120         return value == null ? null : INSTANCE.convertToTimeMillis(value);
121     }
122 
123     @Override
124     public long getTimeMillis(CharSequence name, long defaultValue) {
125         CharSequence value = get0(name);
126         return value == null ? defaultValue : INSTANCE.convertToTimeMillis(value);
127     }
128 
129     @Override
130     public List<String> getAll(String name) {
131         if (isEmpty()) {
132             return Collections.emptyList();
133         }
134         final int nameHash = AsciiString.hashCode(name);
135         List<String> values = new ArrayList<String>(4);
136         for (int i = 0; i < nameValuePairs.length; i += 2) {
137             CharSequence roName = nameValuePairs[i];
138             if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
139                 values.add(nameValuePairs[i + 1].toString());
140             }
141         }
142         return values;
143     }
144 
145     @Override
146     public List<Map.Entry<String, String>> entries() {
147         if (isEmpty()) {
148             return Collections.emptyList();
149         }
150         List<Map.Entry<String, String>> entries = new ArrayList<Map.Entry<String, String>>(size());
151         for (int i = 0; i < nameValuePairs.length; i += 2) {
152             entries.add(new SimpleImmutableEntry<String, String>(nameValuePairs[i].toString(),
153                     nameValuePairs[i + 1].toString())); // [java/index-out-of-bounds]
154         }
155         return entries;
156     }
157 
158     @Override
159     public boolean contains(String name) {
160         return get0(name) != null;
161     }
162 
163     @Override
164     public boolean contains(String name, String value, boolean ignoreCase) {
165         return containsValue(name, value, ignoreCase);
166     }
167 
168     @Override
169     public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
170         if (ignoreCase) {
171             for (int i = 0; i < nameValuePairs.length; i += 2) {
172                 if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
173                         contentEqualsIgnoreCase(nameValuePairs[i + 1], value)) {
174                     return true;
175                 }
176             }
177         } else {
178             for (int i = 0; i < nameValuePairs.length; i += 2) {
179                 if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
180                         contentEquals(nameValuePairs[i + 1], value)) {
181                     return true;
182                 }
183             }
184         }
185         return false;
186     }
187 
188     @Override
189     public Iterator<String> valueStringIterator(CharSequence name) {
190         return new ReadOnlyStringValueIterator(name);
191     }
192 
193     @Override
194     public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
195         return new ReadOnlyValueIterator(name);
196     }
197 
198     @Override
199     public Iterator<Map.Entry<String, String>> iterator() {
200         return new ReadOnlyStringIterator();
201     }
202 
203     @Override
204     public Iterator<Map.Entry<CharSequence, CharSequence>> iteratorCharSequence() {
205         return new ReadOnlyIterator();
206     }
207 
208     @Override
209     public boolean isEmpty() {
210         return nameValuePairs.length == 0;
211     }
212 
213     @Override
214     public int size() {
215         return nameValuePairs.length >>> 1;
216     }
217 
218     @Override
219     public Set<String> names() {
220         if (isEmpty()) {
221             return Collections.emptySet();
222         }
223         Set<String> names = new LinkedHashSet<String>(size());
224         for (int i = 0; i < nameValuePairs.length; i += 2) {
225             names.add(nameValuePairs[i].toString());
226         }
227         return names;
228     }
229 
230     @Override
231     public HttpHeaders add(String name, Object value) {
232         throw new UnsupportedOperationException("read only");
233     }
234 
235     @Override
236     public HttpHeaders add(String name, Iterable<?> values) {
237         throw new UnsupportedOperationException("read only");
238     }
239 
240     @Override
241     public HttpHeaders addInt(CharSequence name, int value) {
242         throw new UnsupportedOperationException("read only");
243     }
244 
245     @Override
246     public HttpHeaders addShort(CharSequence name, short value) {
247         throw new UnsupportedOperationException("read only");
248     }
249 
250     @Override
251     public HttpHeaders set(String name, Object value) {
252         throw new UnsupportedOperationException("read only");
253     }
254 
255     @Override
256     public HttpHeaders set(String name, Iterable<?> values) {
257         throw new UnsupportedOperationException("read only");
258     }
259 
260     @Override
261     public HttpHeaders setInt(CharSequence name, int value) {
262         throw new UnsupportedOperationException("read only");
263     }
264 
265     @Override
266     public HttpHeaders setShort(CharSequence name, short value) {
267         throw new UnsupportedOperationException("read only");
268     }
269 
270     @Override
271     public HttpHeaders remove(String name) {
272         throw new UnsupportedOperationException("read only");
273     }
274 
275     @Override
276     public HttpHeaders clear() {
277         throw new UnsupportedOperationException("read only");
278     }
279 
280     private final class ReadOnlyIterator implements Map.Entry<CharSequence, CharSequence>,
281             Iterator<Map.Entry<CharSequence, CharSequence>> {
282         private CharSequence key;
283         private CharSequence value;
284         private int nextNameIndex;
285 
286         @Override
287         public boolean hasNext() {
288             return nextNameIndex != nameValuePairs.length;
289         }
290 
291         @Override
292         public Map.Entry<CharSequence, CharSequence> next() {
293             if (!hasNext()) {
294                 throw new NoSuchElementException();
295             }
296             key = nameValuePairs[nextNameIndex];
297             value = nameValuePairs[nextNameIndex + 1];
298             nextNameIndex += 2;
299             return this;
300         }
301 
302         @Override
303         public void remove() {
304             throw new UnsupportedOperationException("read only");
305         }
306 
307         @Override
308         public CharSequence getKey() {
309             return key;
310         }
311 
312         @Override
313         public CharSequence getValue() {
314             return value;
315         }
316 
317         @Override
318         public CharSequence setValue(CharSequence value) {
319             throw new UnsupportedOperationException("read only");
320         }
321 
322         @Override
323         public String toString() {
324             return key.toString() + '=' + value.toString();
325         }
326     }
327 
328     private final class ReadOnlyStringIterator implements Map.Entry<String, String>,
329             Iterator<Map.Entry<String, String>> {
330         private String key;
331         private String value;
332         private int nextNameIndex;
333 
334         @Override
335         public boolean hasNext() {
336             return nextNameIndex != nameValuePairs.length;
337         }
338 
339         @Override
340         public Map.Entry<String, String> next() {
341             if (!hasNext()) {
342                 throw new NoSuchElementException();
343             }
344             key = nameValuePairs[nextNameIndex].toString();
345             value = nameValuePairs[nextNameIndex + 1].toString();
346             nextNameIndex += 2;
347             return this;
348         }
349 
350         @Override
351         public void remove() {
352             throw new UnsupportedOperationException("read only");
353         }
354 
355         @Override
356         public String getKey() {
357             return key;
358         }
359 
360         @Override
361         public String getValue() {
362             return value;
363         }
364 
365         @Override
366         public String setValue(String value) {
367             throw new UnsupportedOperationException("read only");
368         }
369 
370         @Override
371         public String toString() {
372             return key + '=' + value;
373         }
374     }
375 
376     private final class ReadOnlyStringValueIterator implements Iterator<String> {
377         private final CharSequence name;
378         private final int nameHash;
379         private int nextNameIndex;
380 
381         ReadOnlyStringValueIterator(CharSequence name) {
382             this.name = name;
383             nameHash = AsciiString.hashCode(name);
384             nextNameIndex = findNextValue();
385         }
386 
387         @Override
388         public boolean hasNext() {
389             return nextNameIndex != -1;
390         }
391 
392         @Override
393         public String next() {
394             if (!hasNext()) {
395                 throw new NoSuchElementException();
396             }
397             String value = nameValuePairs[nextNameIndex + 1].toString();
398             nextNameIndex = findNextValue();
399             return value;
400         }
401 
402         @Override
403         public void remove() {
404             throw new UnsupportedOperationException("read only");
405         }
406 
407         private int findNextValue() {
408             for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
409                 final CharSequence roName = nameValuePairs[i];
410                 if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
411                     return i;
412                 }
413             }
414             return -1;
415         }
416     }
417 
418     private final class ReadOnlyValueIterator implements Iterator<CharSequence> {
419         private final CharSequence name;
420         private final int nameHash;
421         private int nextNameIndex;
422 
423         ReadOnlyValueIterator(CharSequence name) {
424             this.name = name;
425             nameHash = AsciiString.hashCode(name);
426             nextNameIndex = findNextValue();
427         }
428 
429         @Override
430         public boolean hasNext() {
431             return nextNameIndex != -1;
432         }
433 
434         @Override
435         public CharSequence next() {
436             if (!hasNext()) {
437                 throw new NoSuchElementException();
438             }
439             CharSequence value = nameValuePairs[nextNameIndex + 1];
440             nextNameIndex = findNextValue();
441             return value;
442         }
443 
444         @Override
445         public void remove() {
446             throw new UnsupportedOperationException("read only");
447         }
448 
449         private int findNextValue() {
450             for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
451                 final CharSequence roName = nameValuePairs[i];
452                 if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
453                     return i;
454                 }
455             }
456             return -1;
457         }
458     }
459 }