From e81d0e54149b473bc9c14d01450601a03cb0d483 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 17 Nov 2009 05:27:20 +0000 Subject: [PATCH] Related issue: NETTY-251 Add support for HTTP trailing headers * Added DefaultHttpChunkTrailer * Moved header validation logic to HttpCodecUtil --- .../codec/http/DefaultHttpChunkTrailer.java | 133 ++++++++++++++++++ .../codec/http/DefaultHttpMessage.java | 98 +------------ .../handler/codec/http/HttpCodecUtil.java | 86 +++++++++++ 3 files changed, 225 insertions(+), 92 deletions(-) create mode 100644 src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java diff --git a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java new file mode 100644 index 0000000000..450fb72ac2 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java @@ -0,0 +1,133 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.handler.codec.http; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.util.internal.CaseIgnoringComparator; + +/** + * The default {@link HttpChunkTrailer} implementation. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Trustin Lee (trustin@gmail.com) + * @version $Rev$, $Date$ + */ +public class DefaultHttpChunkTrailer implements HttpChunkTrailer { + + // FIXME Lots of code duplication with DefaultHttpMessage + private final Map> headers = new TreeMap>(CaseIgnoringComparator.INSTANCE); + + public boolean isLast() { + return true; + } + + public void addHeader(final String name, final String value) { + validateHeaderName(name); + HttpCodecUtil.validateHeaderValue(value); + if (headers.get(name) == null) { + headers.put(name, new ArrayList(1)); + } + headers.get(name).add(value); + } + + public void setHeader(final String name, final String value) { + validateHeaderName(name); + HttpCodecUtil.validateHeaderValue(value); + List values = new ArrayList(1); + values.add(value); + headers.put(name, values); + } + + public void setHeader(final String name, final Iterable values) { + validateHeaderName(name); + if (values == null) { + throw new NullPointerException("values"); + } + + int nValues = 0; + for (String v: values) { + HttpCodecUtil.validateHeaderValue(v); + nValues ++; + } + + if (nValues == 0) { + throw new IllegalArgumentException("values is empty."); + } + + if (values instanceof List) { + headers.put(name, (List) values); + } else { + List valueList = new LinkedList(); + for (String v: values) { + valueList.add(v); + } + headers.put(name, valueList); + } + } + + private static void validateHeaderName(String name) { + HttpCodecUtil.validateHeaderName(name); + if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) || + name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) || + name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) { + throw new IllegalArgumentException( + "prohibited trailing header: " + name); + } + } + + public void removeHeader(final String name) { + headers.remove(name); + } + + public void clearHeaders() { + headers.clear(); + } + + public String getHeader(final String name) { + List header = headers.get(name); + return header != null && header.size() > 0 ? headers.get(name).get(0) : null; + } + + public List getHeaders(final String name) { + List values = headers.get(name); + if (values == null) { + return Collections.emptyList(); + } else { + return values; + } + } + + public boolean containsHeader(final String name) { + return headers.containsKey(name); + } + + public Set getHeaderNames() { + return headers.keySet(); + } + + public ChannelBuffer getContent() { + return ChannelBuffers.EMPTY_BUFFER; + } +} diff --git a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java index c921fb484e..c803723c1b 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java @@ -52,8 +52,8 @@ public class DefaultHttpMessage implements HttpMessage { } public void addHeader(final String name, final String value) { - validateHeaderName(name); - validateHeaderValue(value); + HttpCodecUtil.validateHeaderName(name); + HttpCodecUtil.validateHeaderValue(value); if (headers.get(name) == null) { headers.put(name, new ArrayList(1)); } @@ -61,22 +61,22 @@ public class DefaultHttpMessage implements HttpMessage { } public void setHeader(final String name, final String value) { - validateHeaderName(name); - validateHeaderValue(value); + HttpCodecUtil.validateHeaderName(name); + HttpCodecUtil.validateHeaderValue(value); List values = new ArrayList(1); values.add(value); headers.put(name, values); } public void setHeader(final String name, final Iterable values) { - validateHeaderName(name); + HttpCodecUtil.validateHeaderName(name); if (values == null) { throw new NullPointerException("values"); } int nValues = 0; for (String v: values) { - validateHeaderValue(v); + HttpCodecUtil.validateHeaderValue(v); nValues ++; } @@ -95,92 +95,6 @@ public class DefaultHttpMessage implements HttpMessage { } } - private static void validateHeaderName(String name) { - if (name == null) { - throw new NullPointerException("name"); - } - for (int i = 0; i < name.length(); i ++) { - char c = name.charAt(i); - if (c > 127) { - throw new IllegalArgumentException( - "name contains non-ascii character: " + name); - } - - // Check prohibited characters. - switch (c) { - case '=': case ',': case ';': case ' ': case ':': - case '\t': case '\r': case '\n': case '\f': - case 0x0b: // Vertical tab - throw new IllegalArgumentException( - "name contains one of the following prohibited characters: " + - "=,;: \\t\\r\\n\\v\\f: " + name); - } - } - } - - private static void validateHeaderValue(String value) { - if (value == null) { - throw new NullPointerException("value"); - } - - // 0 - the previous character was neither CR nor LF - // 1 - the previous character was CR - // 2 - the previous character was LF - int state = 0; - - for (int i = 0; i < value.length(); i ++) { - char c = value.charAt(i); - - // Check the absolutely prohibited characters. - switch (c) { - case '\f': - throw new IllegalArgumentException( - "value contains a prohibited character '\\f': " + value); - case 0x0b: // Vertical tab - throw new IllegalArgumentException( - "value contains a prohibited character '\\v': " + value); - } - - // Check the CRLF (HT | SP) pattern - switch (state) { - case 0: - switch (c) { - case '\r': - state = 1; - break; - case '\n': - state = 2; - break; - } - break; - case 1: - switch (c) { - case '\n': - state = 2; - break; - default: - throw new IllegalArgumentException( - "Only '\\n' is allowed after '\\r': " + value); - } - break; - case 2: - switch (c) { - case ' ': case '\t': - state = 0; - break; - default: - throw new IllegalArgumentException( - "Only ' ' and '\\t' are allowed after '\\n': " + value); - } - } - } - - if (state != 0) { - throw new IllegalArgumentException( - "value must not end with '\\r' or '\\n':" + value); - } - } - public void removeHeader(final String name) { headers.remove(name); } diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpCodecUtil.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpCodecUtil.java index 187bee37e3..2b9f3712ce 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/HttpCodecUtil.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpCodecUtil.java @@ -69,4 +69,90 @@ class HttpCodecUtil { private HttpCodecUtil() { super(); } + + static void validateHeaderName(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + for (int i = 0; i < name.length(); i ++) { + char c = name.charAt(i); + if (c > 127) { + throw new IllegalArgumentException( + "name contains non-ascii character: " + name); + } + + // Check prohibited characters. + switch (c) { + case '=': case ',': case ';': case ' ': case ':': + case '\t': case '\r': case '\n': case '\f': + case 0x0b: // Vertical tab + throw new IllegalArgumentException( + "name contains one of the following prohibited characters: " + + "=,;: \\t\\r\\n\\v\\f: " + name); + } + } + } + + static void validateHeaderValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + + // 0 - the previous character was neither CR nor LF + // 1 - the previous character was CR + // 2 - the previous character was LF + int state = 0; + + for (int i = 0; i < value.length(); i ++) { + char c = value.charAt(i); + + // Check the absolutely prohibited characters. + switch (c) { + case '\f': + throw new IllegalArgumentException( + "value contains a prohibited character '\\f': " + value); + case 0x0b: // Vertical tab + throw new IllegalArgumentException( + "value contains a prohibited character '\\v': " + value); + } + + // Check the CRLF (HT | SP) pattern + switch (state) { + case 0: + switch (c) { + case '\r': + state = 1; + break; + case '\n': + state = 2; + break; + } + break; + case 1: + switch (c) { + case '\n': + state = 2; + break; + default: + throw new IllegalArgumentException( + "Only '\\n' is allowed after '\\r': " + value); + } + break; + case 2: + switch (c) { + case ' ': case '\t': + state = 0; + break; + default: + throw new IllegalArgumentException( + "Only ' ' and '\\t' are allowed after '\\n': " + value); + } + } + } + + if (state != 0) { + throw new IllegalArgumentException( + "value must not end with '\\r' or '\\n':" + value); + } + } }