4c64c98f34
Motivation: We can replace some "hand-rolled" integer checks with our own static utility method to simplify the code. Modifications: Use methods provided by `ObjectUtil`. Result: Cleaner code and less duplication
499 lines
15 KiB
Java
499 lines
15 KiB
Java
/*
|
|
* Copyright 2012 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
package io.netty.handler.codec.http;
|
|
|
|
import io.netty.handler.codec.CharSequenceValueConverter;
|
|
import io.netty.handler.codec.DateFormatter;
|
|
import io.netty.handler.codec.DefaultHeaders;
|
|
import io.netty.handler.codec.DefaultHeaders.NameValidator;
|
|
import io.netty.handler.codec.DefaultHeadersImpl;
|
|
import io.netty.handler.codec.HeadersUtils;
|
|
import io.netty.handler.codec.ValueConverter;
|
|
import io.netty.util.AsciiString;
|
|
import io.netty.util.ByteProcessor;
|
|
import io.netty.util.internal.PlatformDependent;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
|
|
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
|
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
|
|
|
|
/**
|
|
* Default implementation of {@link HttpHeaders}.
|
|
*/
|
|
public class DefaultHttpHeaders extends HttpHeaders {
|
|
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
|
|
private static final ByteProcessor HEADER_NAME_VALIDATOR = new ByteProcessor() {
|
|
@Override
|
|
public boolean process(byte value) throws Exception {
|
|
validateHeaderNameElement(value);
|
|
return true;
|
|
}
|
|
};
|
|
static final NameValidator<CharSequence> HttpNameValidator = new NameValidator<CharSequence>() {
|
|
@Override
|
|
public void validateName(CharSequence name) {
|
|
if (name == null || name.length() == 0) {
|
|
throw new IllegalArgumentException("empty headers are not allowed [" + name + "]");
|
|
}
|
|
if (name instanceof AsciiString) {
|
|
try {
|
|
((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);
|
|
} catch (Exception e) {
|
|
PlatformDependent.throwException(e);
|
|
}
|
|
} else {
|
|
// Go through each character in the name
|
|
for (int index = 0; index < name.length(); ++index) {
|
|
validateHeaderNameElement(name.charAt(index));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
private final DefaultHeaders<CharSequence, CharSequence, ?> headers;
|
|
|
|
public DefaultHttpHeaders() {
|
|
this(true);
|
|
}
|
|
|
|
public DefaultHttpHeaders(boolean validate) {
|
|
this(validate, nameValidator(validate));
|
|
}
|
|
|
|
protected DefaultHttpHeaders(boolean validate, NameValidator<CharSequence> nameValidator) {
|
|
this(new DefaultHeadersImpl<CharSequence, CharSequence>(CASE_INSENSITIVE_HASHER,
|
|
valueConverter(validate),
|
|
nameValidator));
|
|
}
|
|
|
|
protected DefaultHttpHeaders(DefaultHeaders<CharSequence, CharSequence, ?> headers) {
|
|
this.headers = headers;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders add(HttpHeaders headers) {
|
|
if (headers instanceof DefaultHttpHeaders) {
|
|
this.headers.add(((DefaultHttpHeaders) headers).headers);
|
|
return this;
|
|
} else {
|
|
return super.add(headers);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders set(HttpHeaders headers) {
|
|
if (headers instanceof DefaultHttpHeaders) {
|
|
this.headers.set(((DefaultHttpHeaders) headers).headers);
|
|
return this;
|
|
} else {
|
|
return super.set(headers);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders add(String name, Object value) {
|
|
headers.addObject(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders add(CharSequence name, Object value) {
|
|
headers.addObject(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders add(String name, Iterable<?> values) {
|
|
headers.addObject(name, values);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
|
headers.addObject(name, values);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders addInt(CharSequence name, int value) {
|
|
headers.addInt(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders addShort(CharSequence name, short value) {
|
|
headers.addShort(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders remove(String name) {
|
|
headers.remove(name);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders remove(CharSequence name) {
|
|
headers.remove(name);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders set(String name, Object value) {
|
|
headers.setObject(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders set(CharSequence name, Object value) {
|
|
headers.setObject(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders set(String name, Iterable<?> values) {
|
|
headers.setObject(name, values);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
|
headers.setObject(name, values);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders setInt(CharSequence name, int value) {
|
|
headers.setInt(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders setShort(CharSequence name, short value) {
|
|
headers.setShort(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders clear() {
|
|
headers.clear();
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public String get(String name) {
|
|
return get((CharSequence) name);
|
|
}
|
|
|
|
@Override
|
|
public String get(CharSequence name) {
|
|
return HeadersUtils.getAsString(headers, name);
|
|
}
|
|
|
|
@Override
|
|
public Integer getInt(CharSequence name) {
|
|
return headers.getInt(name);
|
|
}
|
|
|
|
@Override
|
|
public int getInt(CharSequence name, int defaultValue) {
|
|
return headers.getInt(name, defaultValue);
|
|
}
|
|
|
|
@Override
|
|
public Short getShort(CharSequence name) {
|
|
return headers.getShort(name);
|
|
}
|
|
|
|
@Override
|
|
public short getShort(CharSequence name, short defaultValue) {
|
|
return headers.getShort(name, defaultValue);
|
|
}
|
|
|
|
@Override
|
|
public Long getTimeMillis(CharSequence name) {
|
|
return headers.getTimeMillis(name);
|
|
}
|
|
|
|
@Override
|
|
public long getTimeMillis(CharSequence name, long defaultValue) {
|
|
return headers.getTimeMillis(name, defaultValue);
|
|
}
|
|
|
|
@Override
|
|
public List<String> getAll(String name) {
|
|
return getAll((CharSequence) name);
|
|
}
|
|
|
|
@Override
|
|
public List<String> getAll(CharSequence name) {
|
|
return HeadersUtils.getAllAsString(headers, name);
|
|
}
|
|
|
|
@Override
|
|
public List<Entry<String, String>> entries() {
|
|
if (isEmpty()) {
|
|
return Collections.emptyList();
|
|
}
|
|
List<Entry<String, String>> entriesConverted = new ArrayList<Entry<String, String>>(
|
|
headers.size());
|
|
for (Entry<String, String> entry : this) {
|
|
entriesConverted.add(entry);
|
|
}
|
|
return entriesConverted;
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
public Iterator<Map.Entry<String, String>> iterator() {
|
|
return HeadersUtils.iteratorAsString(headers);
|
|
}
|
|
|
|
@Override
|
|
public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
|
|
return headers.iterator();
|
|
}
|
|
|
|
@Override
|
|
public Iterator<String> valueStringIterator(CharSequence name) {
|
|
final Iterator<CharSequence> itr = valueCharSequenceIterator(name);
|
|
return new Iterator<String>() {
|
|
@Override
|
|
public boolean hasNext() {
|
|
return itr.hasNext();
|
|
}
|
|
|
|
@Override
|
|
public String next() {
|
|
return itr.next().toString();
|
|
}
|
|
|
|
@Override
|
|
public void remove() {
|
|
itr.remove();
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
|
|
return headers.valueIterator(name);
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(String name) {
|
|
return contains((CharSequence) name);
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(CharSequence name) {
|
|
return headers.contains(name);
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return headers.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return headers.size();
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(String name, String value, boolean ignoreCase) {
|
|
return contains((CharSequence) name, (CharSequence) value, ignoreCase);
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
|
return headers.contains(name, value, ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
|
|
}
|
|
|
|
@Override
|
|
public Set<String> names() {
|
|
return HeadersUtils.namesAsString(headers);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
return o instanceof DefaultHttpHeaders
|
|
&& headers.equals(((DefaultHttpHeaders) o).headers, CASE_SENSITIVE_HASHER);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return headers.hashCode(CASE_SENSITIVE_HASHER);
|
|
}
|
|
|
|
@Override
|
|
public HttpHeaders copy() {
|
|
return new DefaultHttpHeaders(headers.copy());
|
|
}
|
|
|
|
private static void validateHeaderNameElement(byte value) {
|
|
switch (value) {
|
|
case 0x00:
|
|
case '\t':
|
|
case '\n':
|
|
case 0x0b:
|
|
case '\f':
|
|
case '\r':
|
|
case ' ':
|
|
case ',':
|
|
case ':':
|
|
case ';':
|
|
case '=':
|
|
throw new IllegalArgumentException(
|
|
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
|
value);
|
|
default:
|
|
// Check to see if the character is not an ASCII character, or invalid
|
|
if (value < 0) {
|
|
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void validateHeaderNameElement(char value) {
|
|
switch (value) {
|
|
case 0x00:
|
|
case '\t':
|
|
case '\n':
|
|
case 0x0b:
|
|
case '\f':
|
|
case '\r':
|
|
case ' ':
|
|
case ',':
|
|
case ':':
|
|
case ';':
|
|
case '=':
|
|
throw new IllegalArgumentException(
|
|
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
|
value);
|
|
default:
|
|
// Check to see if the character is not an ASCII character, or invalid
|
|
if (value > 127) {
|
|
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
|
|
value);
|
|
}
|
|
}
|
|
}
|
|
|
|
static ValueConverter<CharSequence> valueConverter(boolean validate) {
|
|
return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static NameValidator<CharSequence> nameValidator(boolean validate) {
|
|
return validate ? HttpNameValidator : NameValidator.NOT_NULL;
|
|
}
|
|
|
|
private static class HeaderValueConverter extends CharSequenceValueConverter {
|
|
static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
|
|
|
|
@Override
|
|
public CharSequence convertObject(Object value) {
|
|
if (value instanceof CharSequence) {
|
|
return (CharSequence) value;
|
|
}
|
|
if (value instanceof Date) {
|
|
return DateFormatter.format((Date) value);
|
|
}
|
|
if (value instanceof Calendar) {
|
|
return DateFormatter.format(((Calendar) value).getTime());
|
|
}
|
|
return value.toString();
|
|
}
|
|
}
|
|
|
|
private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
|
|
static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
|
|
|
@Override
|
|
public CharSequence convertObject(Object value) {
|
|
CharSequence seq = super.convertObject(value);
|
|
int state = 0;
|
|
// Start looping through each of the character
|
|
for (int index = 0; index < seq.length(); index++) {
|
|
state = validateValueChar(seq, state, seq.charAt(index));
|
|
}
|
|
|
|
if (state != 0) {
|
|
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
|
|
}
|
|
return seq;
|
|
}
|
|
|
|
private static int validateValueChar(CharSequence seq, int state, char character) {
|
|
/*
|
|
* State:
|
|
* 0: Previous character was neither CR nor LF
|
|
* 1: The previous character was CR
|
|
* 2: The previous character was LF
|
|
*/
|
|
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
|
|
// Check the absolutely prohibited characters.
|
|
switch (character) {
|
|
case 0x0: // NULL
|
|
throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
|
|
case 0x0b: // Vertical tab
|
|
throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
|
|
case '\f':
|
|
throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
|
|
}
|
|
}
|
|
|
|
// Check the CRLF (HT | SP) pattern
|
|
switch (state) {
|
|
case 0:
|
|
switch (character) {
|
|
case '\r':
|
|
return 1;
|
|
case '\n':
|
|
return 2;
|
|
}
|
|
break;
|
|
case 1:
|
|
switch (character) {
|
|
case '\n':
|
|
return 2;
|
|
default:
|
|
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
|
|
}
|
|
case 2:
|
|
switch (character) {
|
|
case '\t':
|
|
case ' ':
|
|
return 0;
|
|
default:
|
|
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
}
|
|
}
|