Merge remote branch 'upstream/master'

This commit is contained in:
Jestan Nirojan 2012-02-08 00:04:03 +05:30
commit fd610f2f28
850 changed files with 14820 additions and 9736 deletions

18
.gitignore vendored
View File

@ -1,10 +1,12 @@
/.project .project
/.classpath .classpath
/.settings .settings
*.iml
*.ipr
*.iws
.idea/
.geany
/target /target
*/target
/reports /reports
/src/main/java/io/netty/util/Version.java */reports
/*.iml
/*.ipr
/*.iws
/*.geany

29
all/assembly.xml Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>all-in-one</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<scope>test</scope>
<unpack>true</unpack>
<useStrictFiltering>true</useStrictFiltering>
<useProjectArtifact>false</useProjectArtifact>
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>${project.groupId}:*</include>
</includes>
<unpackOptions>
<includes>
<include>io/netty/**</include>
</includes>
</unpackOptions>
</dependencySet>
</dependencySets>
</assembly>

117
all/pom.xml Normal file
View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2011 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
</parent>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<packaging>jar</packaging>
<name>Netty/All-in-One</name>
<dependencies>
<!-- The example depends on all modules either directly or transitively -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-example</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.2</version>
<executions>
<execution>
<id>all-in-one</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>${project.basedir}/assembly.xml</descriptor>
</descriptors>
<tarLongFileMode>gnu</tarLongFileMode>
</configuration>
</execution>
</executions>
</plugin>
<!-- Disable all plugin executions configured by jar packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<id>default-resources</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testResources</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.2</version>
<executions>
<execution>
<id>default-test</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>default-jar</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

40
buffer/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2011 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
</parent>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<packaging>jar</packaging>
<name>Netty/Buffer</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -135,7 +135,7 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer {
@Override @Override
public boolean getBoolean(int index) { public boolean getBoolean(int index) {
return (getByte(index) == 1); return getByte(index) != 0;
} }
@Override @Override
@ -278,7 +278,7 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer {
@Override @Override
public boolean readBoolean() { public boolean readBoolean() {
return (readByte() == 1); return readByte() != 0;
} }
@Override @Override

View File

@ -47,7 +47,7 @@ import java.nio.charset.UnsupportedCharsetException;
* *
* <pre> * <pre>
* {@link ChannelBuffer} buffer = ...; * {@link ChannelBuffer} buffer = ...;
* for (int i = 0; i &lt; buffer.capacity(); i ++</strong>) { * for (int i = 0; i &lt; buffer.capacity(); i ++) {
* byte b = buffer.getByte(i); * byte b = buffer.getByte(i);
* System.out.println((char) b); * System.out.println((char) b);
* } * }
@ -130,6 +130,8 @@ import java.nio.charset.UnsupportedCharsetException;
* +-------------------+------------------+------------------+ * +-------------------+------------------+------------------+
* | | | | * | | | |
* 0 <= readerIndex <= writerIndex <= capacity * 0 <= readerIndex <= writerIndex <= capacity
*
*
* AFTER discardReadBytes() * AFTER discardReadBytes()
* *
* +------------------+--------------------------------------+ * +------------------+--------------------------------------+
@ -160,6 +162,8 @@ import java.nio.charset.UnsupportedCharsetException;
* +-------------------+------------------+------------------+ * +-------------------+------------------+------------------+
* | | | | * | | | |
* 0 <= readerIndex <= writerIndex <= capacity * 0 <= readerIndex <= writerIndex <= capacity
*
*
* AFTER clear() * AFTER clear()
* *
* +---------------------------------------------------------+ * +---------------------------------------------------------+

View File

@ -84,7 +84,7 @@ import io.netty.util.CharsetUtil;
* @apiviz.landmark * @apiviz.landmark
* @apiviz.has io.netty.buffer.ChannelBuffer oneway - - creates * @apiviz.has io.netty.buffer.ChannelBuffer oneway - - creates
*/ */
public class ChannelBuffers { public final class ChannelBuffers {
/** /**
* Big endian byte order. * Big endian byte order.
@ -307,7 +307,11 @@ public class ChannelBuffers {
return EMPTY_BUFFER; return EMPTY_BUFFER;
} }
if (buffer.hasArray()) { if (buffer.hasArray()) {
return wrappedBuffer(buffer.order(), buffer.array(), buffer.arrayOffset() + buffer.position(),buffer.remaining()); return wrappedBuffer(
buffer.order(),
buffer.array(),
buffer.arrayOffset() + buffer.position(),
buffer.remaining());
} else { } else {
return new ByteBufferBackedChannelBuffer(buffer); return new ByteBufferBackedChannelBuffer(buffer);
} }

View File

@ -57,9 +57,9 @@ public class DirectChannelBufferFactory extends AbstractChannelBufferFactory {
private final Object bigEndianLock = new Object(); private final Object bigEndianLock = new Object();
private final Object littleEndianLock = new Object(); private final Object littleEndianLock = new Object();
private final int preallocatedBufferCapacity; private final int preallocatedBufferCapacity;
private ChannelBuffer preallocatedBigEndianBuffer = null; private ChannelBuffer preallocatedBigEndianBuffer;
private int preallocatedBigEndianBufferPosition; private int preallocatedBigEndianBufferPosition;
private ChannelBuffer preallocatedLittleEndianBuffer = null; private ChannelBuffer preallocatedLittleEndianBuffer;
private int preallocatedLittleEndianBufferPosition; private int preallocatedLittleEndianBufferPosition;
/** /**

View File

@ -27,7 +27,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.easymock.classextension.EasyMock; import org.easymock.EasyMock;
import org.junit.Test; import org.junit.Test;
/** /**

45
codec-http/pom.xml Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2011 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
</parent>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<packaging>jar</packaging>
<name>Netty/Codec/HTTP</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -32,14 +32,11 @@ import io.netty.buffer.ChannelBuffers;
*/ */
public abstract class AbstractDiskHttpData extends AbstractHttpData { public abstract class AbstractDiskHttpData extends AbstractHttpData {
protected File file = null; protected File file;
private boolean isRenamed;
private FileChannel fileChannel;
private boolean isRenamed = false; public AbstractDiskHttpData(String name, Charset charset, long size) {
private FileChannel fileChannel = null;
public AbstractDiskHttpData(String name, Charset charset, long size)
throws NullPointerException, IllegalArgumentException {
super(name, charset, size); super(name, charset, size);
} }

View File

@ -23,17 +23,12 @@ import java.nio.charset.Charset;
public abstract class AbstractHttpData implements HttpData { public abstract class AbstractHttpData implements HttpData {
protected final String name; protected final String name;
protected long definedSize;
protected long definedSize = 0; protected long size;
protected long size = 0;
protected Charset charset = HttpCodecUtil.DEFAULT_CHARSET; protected Charset charset = HttpCodecUtil.DEFAULT_CHARSET;
protected boolean completed;
protected boolean completed = false; public AbstractHttpData(String name, Charset charset, long size) {
public AbstractHttpData(String name, Charset charset, long size)
throws NullPointerException, IllegalArgumentException {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }

View File

@ -32,14 +32,11 @@ import io.netty.buffer.ChannelBuffers;
*/ */
public abstract class AbstractMemoryHttpData extends AbstractHttpData { public abstract class AbstractMemoryHttpData extends AbstractHttpData {
private ChannelBuffer channelBuffer = null; private ChannelBuffer channelBuffer;
private int chunkPosition;
protected boolean isRenamed;
private int chunkPosition = 0; public AbstractMemoryHttpData(String name, Charset charset, long size) {
protected boolean isRenamed = false;
public AbstractMemoryHttpData(String name, Charset charset, long size)
throws NullPointerException, IllegalArgumentException {
super(name, charset, size); super(name, charset, size);
} }

View File

@ -18,8 +18,6 @@ package io.netty.handler.codec.http;
import java.io.Serializable; import java.io.Serializable;
import java.util.Comparator; import java.util.Comparator;
/**
*/
final class CaseIgnoringComparator implements Comparator<String>, Serializable { final class CaseIgnoringComparator implements Comparator<String>, Serializable {
private static final long serialVersionUID = 4582133183775373862L; private static final long serialVersionUID = 4582133183775373862L;

View File

@ -41,10 +41,10 @@ import java.util.regex.Pattern;
*/ */
public class CookieDecoder { public class CookieDecoder {
private final static Pattern PATTERN = private static final Pattern PATTERN =
Pattern.compile("(?:\\s|[;,])*\\$*([^;=]+)(?:=(?:[\"']((?:\\\\.|[^\"])*)[\"']|([^;,]*)))?(\\s*(?:[;,]+\\s*|$))"); Pattern.compile("(?:\\s|[;,])*\\$*([^;=]+)(?:=(?:[\"']((?:\\\\.|[^\"])*)[\"']|([^;,]*)))?(\\s*(?:[;,]+\\s*|$))");
private final static String COMMA = ","; private static final String COMMA = ",";
private final boolean lenient; private final boolean lenient;

View File

@ -204,8 +204,10 @@ public class CookieEncoder {
} }
} }
if(sb.length() > 0) if (sb.length() > 0) {
sb.setLength(sb.length() - 1); sb.setLength(sb.length() - 1);
}
return sb.toString(); return sb.toString();
} }

View File

@ -15,8 +15,6 @@
*/ */
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
/**
*/
final class CookieHeaderNames { final class CookieHeaderNames {
static final String PATH = "Path"; static final String PATH = "Path";

View File

@ -35,11 +35,11 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
*/ */
public static long MINSIZE = 0x4000; public static long MINSIZE = 0x4000;
private boolean useDisk = false; private boolean useDisk;
private boolean checkSize = false; private boolean checkSize;
private long minSize = 0L; private long minSize;
/** /**
* Keep all HttpDatas until cleanAllHttpDatas() is called. * Keep all HttpDatas until cleanAllHttpDatas() is called.
@ -91,8 +91,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
} }
@Override @Override
public Attribute createAttribute(HttpRequest request, String name) throws NullPointerException, public Attribute createAttribute(HttpRequest request, String name) {
IllegalArgumentException {
if (useDisk) { if (useDisk) {
Attribute attribute = new DiskAttribute(name); Attribute attribute = new DiskAttribute(name);
List<HttpData> fileToDelete = getList(request); List<HttpData> fileToDelete = getList(request);
@ -107,12 +106,8 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
return new MemoryAttribute(name); return new MemoryAttribute(name);
} }
/* (non-Javadoc)
* @see io.netty.handler.codec.http2.HttpDataFactory#createAttribute(java.lang.String, java.lang.String)
*/
@Override @Override
public Attribute createAttribute(HttpRequest request, String name, String value) public Attribute createAttribute(HttpRequest request, String name, String value) {
throws NullPointerException, IllegalArgumentException {
if (useDisk) { if (useDisk) {
Attribute attribute; Attribute attribute;
try { try {
@ -137,13 +132,10 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
} }
} }
/* (non-Javadoc)
* @see io.netty.handler.codec.http2.HttpDataFactory#createFileUpload(java.lang.String, java.lang.String, java.lang.String)
*/
@Override @Override
public FileUpload createFileUpload(HttpRequest request, String name, String filename, public FileUpload createFileUpload(HttpRequest request, String name, String filename,
String contentType, String contentTransferEncoding, Charset charset, String contentType, String contentTransferEncoding, Charset charset,
long size) throws NullPointerException, IllegalArgumentException { long size) {
if (useDisk) { if (useDisk) {
FileUpload fileUpload = new DiskFileUpload(name, filename, contentType, FileUpload fileUpload = new DiskFileUpload(name, filename, contentType,
contentTransferEncoding, charset, size); contentTransferEncoding, charset, size);

View File

@ -96,8 +96,7 @@ public class DefaultHttpMessage implements HttpMessage {
@Override @Override
public String getHeader(final String name) { public String getHeader(final String name) {
List<String> values = getHeaders(name); return headers.getHeader(name);
return values.size() > 0 ? values.get(0) : null;
} }
@Override @Override

View File

@ -24,7 +24,7 @@ import io.netty.buffer.ChannelBuffers;
* Disk implementation of Attributes * Disk implementation of Attributes
*/ */
public class DiskAttribute extends AbstractDiskHttpData implements Attribute { public class DiskAttribute extends AbstractDiskHttpData implements Attribute {
public static String baseDirectory = null; public static String baseDirectory;
public static boolean deleteOnExitTemporaryFile = true; public static boolean deleteOnExitTemporaryFile = true;
@ -47,8 +47,7 @@ public class DiskAttribute extends AbstractDiskHttpData implements Attribute {
* @throws IllegalArgumentException * @throws IllegalArgumentException
* @throws IOException * @throws IOException
*/ */
public DiskAttribute(String name, String value) public DiskAttribute(String name, String value) throws IOException {
throws NullPointerException, IllegalArgumentException, IOException {
super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); // Attribute have no default size super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); // Attribute have no default size
setValue(value); setValue(value);
} }

View File

@ -22,7 +22,7 @@ import java.nio.charset.Charset;
* Disk FileUpload implementation that stores file into real files * Disk FileUpload implementation that stores file into real files
*/ */
public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload { public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload {
public static String baseDirectory = null; public static String baseDirectory;
public static boolean deleteOnExitTemporaryFile = true; public static boolean deleteOnExitTemporaryFile = true;
@ -30,15 +30,14 @@ public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload {
public static String postfix = ".tmp"; public static String postfix = ".tmp";
private String filename = null; private String filename;
private String contentType = null; private String contentType;
private String contentTransferEncoding = null; private String contentTransferEncoding;
public DiskFileUpload(String name, String filename, String contentType, public DiskFileUpload(String name, String filename, String contentType,
String contentTransferEncoding, Charset charset, long size) String contentTransferEncoding, Charset charset, long size) {
throws NullPointerException, IllegalArgumentException {
super(name, charset, size); super(name, charset, size);
setFilename(filename); setFilename(filename);
setContentType(contentType); setContentType(contentType);
@ -50,17 +49,11 @@ public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload {
return HttpDataType.FileUpload; return HttpDataType.FileUpload;
} }
/* (non-Javadoc)
* @see io.netty.handler.codec.http2.FileUpload#getFilename()
*/
@Override @Override
public String getFilename() { public String getFilename() {
return filename; return filename;
} }
/* (non-Javadoc)
* @see io.netty.handler.codec.http2.FileUpload#setFilename(java.lang.String)
*/
@Override @Override
public void setFilename(String filename) { public void setFilename(String filename) {
if (filename == null) { if (filename == null) {

View File

@ -20,9 +20,7 @@ import java.util.List;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
/** final class HttpCodecUtil {
*/
class HttpCodecUtil {
//space ' ' //space ' '
static final byte SP = 32; static final byte SP = 32;

View File

@ -30,16 +30,20 @@ import io.netty.handler.codec.embedder.EncoderEmbedder;
public class HttpContentCompressor extends HttpContentEncoder { public class HttpContentCompressor extends HttpContentEncoder {
private final int compressionLevel; private final int compressionLevel;
private final int windowBits;
private final int memLevel;
/** /**
* Creates a new handler with the default compression level (<tt>6</tt>). * Creates a new handler with the default compression level (<tt>6</tt>),
* default window size (<tt>15</tt>) and default memory level (<tt>8</tt>).
*/ */
public HttpContentCompressor() { public HttpContentCompressor() {
this(6); this(6);
} }
/** /**
* Creates a new handler with the specified compression level. * Creates a new handler with the specified compression level, default
* window size (<tt>15</tt>) and default memory level (<tt>8</tt>).
* *
* @param compressionLevel * @param compressionLevel
* {@code 1} yields the fastest compression and {@code 9} yields the * {@code 1} yields the fastest compression and {@code 9} yields the
@ -47,12 +51,45 @@ public class HttpContentCompressor extends HttpContentEncoder {
* compression level is {@code 6}. * compression level is {@code 6}.
*/ */
public HttpContentCompressor(int compressionLevel) { public HttpContentCompressor(int compressionLevel) {
this(compressionLevel, 15, 8);
}
/**
* Creates a new handler with the specified compression level, window size,
* and memory level..
*
* @param compressionLevel
* {@code 1} yields the fastest compression and {@code 9} yields the
* best compression. {@code 0} means no compression. The default
* compression level is {@code 6}.
* @param windowBits
* The base two logarithm of the size of the history buffer. The
* value should be in the range {@code 9} to {@code 15} inclusive.
* Larger values result in better compression at the expense of
* memory usage. The default value is {@code 15}.
* @param memLevel
* How much memory should be allocated for the internal compression
* state. {@code 1} uses minimum memory and {@code 9} uses maximum
* memory. Larger values result in better and faster compression
* at the expense of memory usage. The default value is {@code 8}
*/
public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel) {
if (compressionLevel < 0 || compressionLevel > 9) { if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + "compressionLevel: " + compressionLevel +
" (expected: 0-9)"); " (expected: 0-9)");
} }
if (windowBits < 9 || windowBits > 15) {
throw new IllegalArgumentException(
"windowBits: " + windowBits + " (expected: 9-15)");
}
if (memLevel < 1 || memLevel > 9) {
throw new IllegalArgumentException(
"memLevel: " + memLevel + " (expected: 1-9)");
}
this.compressionLevel = compressionLevel; this.compressionLevel = compressionLevel;
this.windowBits = windowBits;
this.memLevel = memLevel;
} }
@Override @Override
@ -83,17 +120,47 @@ public class HttpContentCompressor extends HttpContentEncoder {
return new Result( return new Result(
targetContentEncoding, targetContentEncoding,
new EncoderEmbedder<ChannelBuffer>( new EncoderEmbedder<ChannelBuffer>(
new ZlibEncoder(wrapper, compressionLevel))); new ZlibEncoder(wrapper, compressionLevel, windowBits, memLevel)));
} }
private ZlibWrapper determineWrapper(String acceptEncoding) { protected ZlibWrapper determineWrapper(String acceptEncoding) {
// FIXME: Use the Q value. float starQ = -1.0f;
if (acceptEncoding.indexOf("gzip") >= 0) { float gzipQ = -1.0f;
float deflateQ = -1.0f;
for (String encoding : acceptEncoding.split(",")) {
float q = 1.0f;
int equalsPos = encoding.indexOf('=');
if (equalsPos != -1) {
try {
q = Float.valueOf(encoding.substring(equalsPos + 1));
} catch (NumberFormatException e) {
// Ignore encoding
q = 0.0f;
}
}
if (encoding.indexOf("*") >= 0) {
starQ = q;
} else if (encoding.indexOf("gzip") >= 0 && q > gzipQ) {
gzipQ = q;
} else if (encoding.indexOf("deflate") >= 0 && q > deflateQ) {
deflateQ = q;
}
}
if (gzipQ > 0.0f || deflateQ > 0.0f) {
if (gzipQ >= deflateQ) {
return ZlibWrapper.GZIP;
} else {
return ZlibWrapper.ZLIB;
}
}
if (starQ > 0.0f) {
if (gzipQ == -1.0f) {
return ZlibWrapper.GZIP; return ZlibWrapper.GZIP;
} }
if (acceptEncoding.indexOf("deflate") >= 0) { if (deflateQ == -1.0f) {
return ZlibWrapper.ZLIB; return ZlibWrapper.ZLIB;
} }
}
return null; return null;
} }
} }

View File

@ -29,8 +29,7 @@ public interface HttpDataFactory {
* @throws NullPointerException * @throws NullPointerException
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
Attribute createAttribute(HttpRequest request, String name) Attribute createAttribute(HttpRequest request, String name);
throws NullPointerException, IllegalArgumentException;
/** /**
* *
@ -41,8 +40,7 @@ public interface HttpDataFactory {
* @throws NullPointerException * @throws NullPointerException
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
Attribute createAttribute(HttpRequest request, String name, String value) Attribute createAttribute(HttpRequest request, String name, String value);
throws NullPointerException, IllegalArgumentException;
/** /**
* *
@ -56,7 +54,7 @@ public interface HttpDataFactory {
*/ */
FileUpload createFileUpload(HttpRequest request, String name, String filename, FileUpload createFileUpload(HttpRequest request, String name, String filename,
String contentType, String contentTransferEncoding, Charset charset, String contentType, String contentTransferEncoding, Charset charset,
long size) throws NullPointerException, IllegalArgumentException; long size);
/** /**
* Remove the given InterfaceHttpData from clean list (will not delete the file, except if the file * Remove the given InterfaceHttpData from clean list (will not delete the file, except if the file

View File

@ -568,11 +568,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
if (nextByte == HttpCodecUtil.LF) { if (nextByte == HttpCodecUtil.LF) {
return sb.toString(); return sb.toString();
} }
} } else if (nextByte == HttpCodecUtil.LF) {
else if (nextByte == HttpCodecUtil.LF) {
return sb.toString(); return sb.toString();
} } else {
else {
if (lineLength >= maxLineLength) { if (lineLength >= maxLineLength) {
// TODO: Respond with Bad Request and discard the traffic // TODO: Respond with Bad Request and discard the traffic
// or close the connection. // or close the connection.

View File

@ -25,6 +25,8 @@ import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers; import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.oneone.OneToOneEncoder; import io.netty.handler.codec.oneone.OneToOneEncoder;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
@ -59,7 +61,17 @@ public abstract class HttpMessageEncoder extends OneToOneEncoder {
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
if (msg instanceof HttpMessage) { if (msg instanceof HttpMessage) {
HttpMessage m = (HttpMessage) msg; HttpMessage m = (HttpMessage) msg;
boolean chunked = this.chunked = HttpCodecUtil.isTransferEncodingChunked(m); boolean chunked;
if (m.isChunked()) {
// check if the Transfer-Encoding is set to chunked already.
// if not add the header to the message
if (!HttpCodecUtil.isTransferEncodingChunked(m)) {
m.addHeader(Names.TRANSFER_ENCODING, Values.CHUNKED);
}
chunked = this.chunked = true;
} else {
chunked = this.chunked = HttpCodecUtil.isTransferEncodingChunked(m);
}
ChannelBuffer header = ChannelBuffers.dynamicBuffer( ChannelBuffer header = ChannelBuffers.dynamicBuffer(
channel.getConfig().getBufferFactory()); channel.getConfig().getBufferFactory());
encodeInitialLine(header, m); encodeInitialLine(header, m);

View File

@ -23,7 +23,8 @@ import io.netty.util.CharsetUtil;
/** /**
* Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder * Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder
*/ */
public class HttpPostBodyUtil { final class HttpPostBodyUtil {
public static int chunkSize = 8096; public static int chunkSize = 8096;
/** /**
* HTTP content disposition header name. * HTTP content disposition header name.

View File

@ -50,12 +50,12 @@ public class HttpPostRequestDecoder {
/** /**
* Does request have a body to decode * Does request have a body to decode
*/ */
private boolean bodyToDecode = false; private boolean bodyToDecode;
/** /**
* Does the last chunk already received * Does the last chunk already received
*/ */
private boolean isLastChunk = false; private boolean isLastChunk;
/** /**
* HttpDatas from Body * HttpDatas from Body
@ -71,28 +71,28 @@ public class HttpPostRequestDecoder {
/** /**
* The current channelBuffer * The current channelBuffer
*/ */
private ChannelBuffer undecodedChunk = null; private ChannelBuffer undecodedChunk;
/** /**
* Does this request is a Multipart request * Does this request is a Multipart request
*/ */
private boolean isMultipart = false; private boolean isMultipart;
/** /**
* Body HttpDatas current position * Body HttpDatas current position
*/ */
private int bodyListHttpDataRank = 0; private int bodyListHttpDataRank;
/** /**
* If multipart, this is the boundary for the flobal multipart * If multipart, this is the boundary for the flobal multipart
*/ */
private String multipartDataBoundary = null; private String multipartDataBoundary;
/** /**
* If multipart, there could be internal multiparts (mixed) to the global multipart. * If multipart, there could be internal multiparts (mixed) to the global multipart.
* Only one level is allowed. * Only one level is allowed.
*/ */
private String multipartMixedBoundary = null; private String multipartMixedBoundary;
/** /**
* Current status * Current status
@ -102,17 +102,17 @@ public class HttpPostRequestDecoder {
/** /**
* Used in Multipart * Used in Multipart
*/ */
private Map<String, Attribute> currentFieldAttributes = null; private Map<String, Attribute> currentFieldAttributes;
/** /**
* The current FileUpload that is currently in decode process * The current FileUpload that is currently in decode process
*/ */
private FileUpload currentFileUpload = null; private FileUpload currentFileUpload;
/** /**
* The current Attribute that is currently in decode process * The current Attribute that is currently in decode process
*/ */
private Attribute currentAttribute = null; private Attribute currentAttribute;
/** /**
* *
@ -122,8 +122,7 @@ public class HttpPostRequestDecoder {
* @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
*/ */
public HttpPostRequestDecoder(HttpRequest request) public HttpPostRequestDecoder(HttpRequest request)
throws ErrorDataDecoderException, IncompatibleDataDecoderException, throws ErrorDataDecoderException, IncompatibleDataDecoderException {
NullPointerException {
this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
request, HttpCodecUtil.DEFAULT_CHARSET); request, HttpCodecUtil.DEFAULT_CHARSET);
} }
@ -137,8 +136,7 @@ public class HttpPostRequestDecoder {
* @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
*/ */
public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request) public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request)
throws ErrorDataDecoderException, IncompatibleDataDecoderException, throws ErrorDataDecoderException, IncompatibleDataDecoderException {
NullPointerException {
this(factory, request, HttpCodecUtil.DEFAULT_CHARSET); this(factory, request, HttpCodecUtil.DEFAULT_CHARSET);
} }
@ -153,7 +151,7 @@ public class HttpPostRequestDecoder {
*/ */
public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request,
Charset charset) throws ErrorDataDecoderException, Charset charset) throws ErrorDataDecoderException,
IncompatibleDataDecoderException, NullPointerException { IncompatibleDataDecoderException {
if (factory == null) { if (factory == null) {
throw new NullPointerException("factory"); throw new NullPointerException("factory");
} }
@ -452,6 +450,16 @@ public class HttpPostRequestDecoder {
charset); charset);
currentAttribute = factory.createAttribute(request, key); currentAttribute = factory.createAttribute(request, key);
firstpos = currentpos; firstpos = currentpos;
} else if (read == '&') { // special empty FIELD
currentStatus = MultiPartStatus.DISPOSITION;
ampersandpos = currentpos - 1;
String key = decodeAttribute(undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
currentAttribute = factory.createAttribute(request, key);
currentAttribute.setValue(""); // empty
addHttpData(currentAttribute);
currentAttribute = null;
firstpos = currentpos;
contRead = true;
} }
break; break;
case FIELD:// search '&' or end of line case FIELD:// search '&' or end of line

View File

@ -51,16 +51,16 @@ public class HttpPostRequestEncoder implements ChunkedInput {
/** /**
* Chunked false by default * Chunked false by default
*/ */
private boolean isChunked = false; private boolean isChunked;
/** /**
* InterfaceHttpData for Body (without encoding) * InterfaceHttpData for Body (without encoding)
*/ */
private List<InterfaceHttpData> bodyListDatas = null; private List<InterfaceHttpData> bodyListDatas;
/** /**
* The final Multipart List of InterfaceHttpData including encoding * The final Multipart List of InterfaceHttpData including encoding
*/ */
private List<InterfaceHttpData> multipartHttpDatas = null; private List<InterfaceHttpData> multipartHttpDatas;
/** /**
* Does this request is a Multipart request * Does this request is a Multipart request
@ -70,17 +70,17 @@ public class HttpPostRequestEncoder implements ChunkedInput {
/** /**
* If multipart, this is the boundary for the flobal multipart * If multipart, this is the boundary for the flobal multipart
*/ */
private String multipartDataBoundary = null; private String multipartDataBoundary;
/** /**
* If multipart, there could be internal multiparts (mixed) to the global multipart. * If multipart, there could be internal multiparts (mixed) to the global multipart.
* Only one level is allowed. * Only one level is allowed.
*/ */
private String multipartMixedBoundary = null; private String multipartMixedBoundary;
/** /**
* To check if the header has been finalized * To check if the header has been finalized
*/ */
private boolean headerFinalized = false; private boolean headerFinalized;
/** /**
* *
@ -90,7 +90,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the request is not a POST * @throws ErrorDataEncoderException if the request is not a POST
*/ */
public HttpPostRequestEncoder(HttpRequest request, boolean multipart) public HttpPostRequestEncoder(HttpRequest request, boolean multipart)
throws ErrorDataEncoderException, NullPointerException { throws ErrorDataEncoderException {
this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
request, multipart, HttpCodecUtil.DEFAULT_CHARSET); request, multipart, HttpCodecUtil.DEFAULT_CHARSET);
} }
@ -104,7 +104,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the request is not a POST * @throws ErrorDataEncoderException if the request is not a POST
*/ */
public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart) public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart)
throws ErrorDataEncoderException, NullPointerException { throws ErrorDataEncoderException {
this(factory, request, multipart, HttpCodecUtil.DEFAULT_CHARSET); this(factory, request, multipart, HttpCodecUtil.DEFAULT_CHARSET);
} }
@ -118,8 +118,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the request is not a POST * @throws ErrorDataEncoderException if the request is not a POST
*/ */
public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request,
boolean multipart, Charset charset) throws ErrorDataEncoderException, boolean multipart, Charset charset) throws ErrorDataEncoderException {
NullPointerException {
if (factory == null) { if (factory == null) {
throw new NullPointerException("factory"); throw new NullPointerException("factory");
} }
@ -157,24 +156,24 @@ public class HttpPostRequestEncoder implements ChunkedInput {
/** /**
* Does the last non empty chunk already encoded so that next chunk will be empty (last chunk) * Does the last non empty chunk already encoded so that next chunk will be empty (last chunk)
*/ */
private boolean isLastChunk = false; private boolean isLastChunk;
/** /**
* Last chunk already sent * Last chunk already sent
*/ */
private boolean isLastChunkSent = false; private boolean isLastChunkSent;
/** /**
* The current FileUpload that is currently in encode process * The current FileUpload that is currently in encode process
*/ */
private FileUpload currentFileUpload = null; private FileUpload currentFileUpload;
/** /**
* While adding a FileUpload, is the multipart currently in Mixed Mode * While adding a FileUpload, is the multipart currently in Mixed Mode
*/ */
private boolean duringMixedMode = false; private boolean duringMixedMode;
/** /**
* Global Body size * Global Body size
*/ */
private long globalBodySize = 0; private long globalBodySize;
/** /**
* True if this request is a Multipart request * True if this request is a Multipart request
@ -224,7 +223,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done * @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done
*/ */
public void setBodyHttpDatas(List<InterfaceHttpData> datas) public void setBodyHttpDatas(List<InterfaceHttpData> datas)
throws NullPointerException, ErrorDataEncoderException { throws ErrorDataEncoderException {
if (datas == null) { if (datas == null) {
throw new NullPointerException("datas"); throw new NullPointerException("datas");
} }
@ -246,7 +245,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done * @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done
*/ */
public void addBodyAttribute(String name, String value) public void addBodyAttribute(String name, String value)
throws NullPointerException, ErrorDataEncoderException { throws ErrorDataEncoderException {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -268,7 +267,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done * @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done
*/ */
public void addBodyFileUpload(String name, File file, String contentType, boolean isText) public void addBodyFileUpload(String name, File file, String contentType, boolean isText)
throws NullPointerException, ErrorDataEncoderException { throws ErrorDataEncoderException {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -307,7 +306,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done * @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done
*/ */
public void addBodyFileUploads(String name, File[] file, String[] contentType, boolean[] isText) public void addBodyFileUploads(String name, File[] file, String[] contentType, boolean[] isText)
throws NullPointerException, ErrorDataEncoderException { throws ErrorDataEncoderException {
if (file.length != contentType.length && file.length != isText.length) { if (file.length != contentType.length && file.length != isText.length) {
throw new NullPointerException("Different array length"); throw new NullPointerException("Different array length");
} }
@ -323,7 +322,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
* @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done * @throws ErrorDataEncoderException if the encoding is in error or if the finalize were already done
*/ */
public void addBodyHttpData(InterfaceHttpData data) public void addBodyHttpData(InterfaceHttpData data)
throws NullPointerException, ErrorDataEncoderException { throws ErrorDataEncoderException {
if (headerFinalized) { if (headerFinalized) {
throw new ErrorDataEncoderException("Cannot add value once finalized"); throw new ErrorDataEncoderException("Cannot add value once finalized");
} }
@ -554,7 +553,7 @@ public class HttpPostRequestEncoder implements ChunkedInput {
/** /**
* Iterator to be used when encoding will be called chunk after chunk * Iterator to be used when encoding will be called chunk after chunk
*/ */
private ListIterator<InterfaceHttpData> iterator = null; private ListIterator<InterfaceHttpData> iterator;
/** /**
* Finalize the request by preparing the Header in the request and * Finalize the request by preparing the Header in the request and
@ -675,11 +674,11 @@ public class HttpPostRequestEncoder implements ChunkedInput {
/** /**
* The ChannelBuffer currently used by the encoder * The ChannelBuffer currently used by the encoder
*/ */
private ChannelBuffer currentBuffer = null; private ChannelBuffer currentBuffer;
/** /**
* The current InterfaceHttpData to encode (used if more chunks are available) * The current InterfaceHttpData to encode (used if more chunks are available)
*/ */
private InterfaceHttpData currentData = null; private InterfaceHttpData currentData;
/** /**
* If not multipart, does the currentBuffer stands for the Key or for the Value * If not multipart, does the currentBuffer stands for the Key or for the Value
*/ */

View File

@ -36,8 +36,7 @@ public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute
* @throws IllegalArgumentException * @throws IllegalArgumentException
* @throws IOException * @throws IOException
*/ */
public MemoryAttribute(String name, String value) public MemoryAttribute(String name, String value) throws IOException {
throws NullPointerException, IllegalArgumentException, IOException {
super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); // Attribute have no default size super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); // Attribute have no default size
setValue(value); setValue(value);
} }

View File

@ -24,15 +24,14 @@ import java.nio.charset.Charset;
*/ */
public class MemoryFileUpload extends AbstractMemoryHttpData implements FileUpload { public class MemoryFileUpload extends AbstractMemoryHttpData implements FileUpload {
private String filename = null; private String filename;
private String contentType = null; private String contentType;
private String contentTransferEncoding = null; private String contentTransferEncoding;
public MemoryFileUpload(String name, String filename, String contentType, public MemoryFileUpload(String name, String filename, String contentType,
String contentTransferEncoding, Charset charset, long size) String contentTransferEncoding, Charset charset, long size) {
throws NullPointerException, IllegalArgumentException {
super(name, charset, size); super(name, charset, size);
setFilename(filename); setFilename(filename);
setContentType(contentType); setContentType(contentType);

View File

@ -26,20 +26,16 @@ import io.netty.buffer.ChannelBuffer;
* Mixed implementation using both in Memory and in File with a limit of size * Mixed implementation using both in Memory and in File with a limit of size
*/ */
public class MixedAttribute implements Attribute { public class MixedAttribute implements Attribute {
private Attribute attribute = null; private Attribute attribute;
private long limitSize = 0; private long limitSize;
public MixedAttribute(String name, public MixedAttribute(String name, long limitSize) {
long limitSize) throws NullPointerException,
IllegalArgumentException {
this.limitSize = limitSize; this.limitSize = limitSize;
attribute = new MemoryAttribute(name); attribute = new MemoryAttribute(name);
} }
public MixedAttribute(String name, String value, public MixedAttribute(String name, String value, long limitSize) {
long limitSize) throws NullPointerException,
IllegalArgumentException {
this.limitSize = limitSize; this.limitSize = limitSize;
if (value.length() > this.limitSize) { if (value.length() > this.limitSize) {
try { try {
@ -62,8 +58,7 @@ public class MixedAttribute implements Attribute {
} }
@Override @Override
public void addContent(ChannelBuffer buffer, boolean last) public void addContent(ChannelBuffer buffer, boolean last) throws IOException {
throws IOException {
if (attribute instanceof MemoryAttribute) { if (attribute instanceof MemoryAttribute) {
if (attribute.length() + buffer.readableBytes() > limitSize) { if (attribute.length() + buffer.readableBytes() > limitSize) {
DiskAttribute diskAttribute = new DiskAttribute(attribute DiskAttribute diskAttribute = new DiskAttribute(attribute

View File

@ -26,16 +26,15 @@ import io.netty.buffer.ChannelBuffer;
* Mixed implementation using both in Memory and in File with a limit of size * Mixed implementation using both in Memory and in File with a limit of size
*/ */
public class MixedFileUpload implements FileUpload { public class MixedFileUpload implements FileUpload {
private FileUpload fileUpload = null; private FileUpload fileUpload;
private long limitSize = 0; private long limitSize;
private long definedSize = 0; private long definedSize;
public MixedFileUpload(String name, String filename, String contentType, public MixedFileUpload(String name, String filename, String contentType,
String contentTransferEncoding, Charset charset, long size, String contentTransferEncoding, Charset charset, long size,
long limitSize) throws NullPointerException, long limitSize) {
IllegalArgumentException {
this.limitSize = limitSize; this.limitSize = limitSize;
if (size > this.limitSize) { if (size > this.limitSize) {
fileUpload = new DiskFileUpload(name, filename, contentType, fileUpload = new DiskFileUpload(name, filename, contentType,

View File

@ -32,10 +32,25 @@ import io.netty.util.CharsetUtil;
* <pre> * <pre>
* {@link QueryStringDecoder} decoder = new {@link QueryStringDecoder}("/hello?recipient=world&x=1;y=2"); * {@link QueryStringDecoder} decoder = new {@link QueryStringDecoder}("/hello?recipient=world&x=1;y=2");
* assert decoder.getPath().equals("/hello"); * assert decoder.getPath().equals("/hello");
* assert decoder.getParameters().get("recipient").equals("world"); * assert decoder.getParameters().get("recipient").get(0).equals("world");
* assert decoder.getParameters().get("x").equals("1"); * assert decoder.getParameters().get("x").get(0).equals("1");
* assert decoder.getParameters().get("y").equals("2"); * assert decoder.getParameters().get("y").get(0).equals("2");
* </pre> * </pre>
*
* This decoder can also decode the content of an HTTP POST request whose
* content type is <tt>application/x-www-form-urlencoded</tt>:
* <pre>
* {@link QueryStringDecoder} decoder = new {@link QueryStringDecoder}("recipient=world&x=1;y=2", false);
* ...
* </pre>
*
* <h3>HashDOS vulnerability fix</h3>
*
* As a workaround to the <a href="http://events.ccc.de/congress/2011/Fahrplan/attachments/2007_28C3_Effective_DoS_on_web_application_platforms.pdf">HashDOS</a>
* vulnerability, the decoder limits the maximum number of decoded key-value
* parameter pairs, up to {@literal 1024} by default, and you can configure it
* when you construct the decoder by passing an additional integer parameter.
*
* @see QueryStringEncoder * @see QueryStringEncoder
* *
* @apiviz.stereotype utility * @apiviz.stereotype utility
@ -43,10 +58,15 @@ import io.netty.util.CharsetUtil;
*/ */
public class QueryStringDecoder { public class QueryStringDecoder {
private static final int DEFAULT_MAX_PARAMS = 1024;
private final Charset charset; private final Charset charset;
private final String uri; private final String uri;
private final boolean hasPath;
private final int maxParams;
private String path; private String path;
private Map<String, List<String>> params; private Map<String, List<String>> params;
private int nParams;
/** /**
* Creates a new decoder that decodes the specified URI. The decoder will * Creates a new decoder that decodes the specified URI. The decoder will
@ -56,21 +76,51 @@ public class QueryStringDecoder {
this(uri, HttpCodecUtil.DEFAULT_CHARSET); this(uri, HttpCodecUtil.DEFAULT_CHARSET);
} }
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(String uri, boolean hasPath) {
this(uri, HttpCodecUtil.DEFAULT_CHARSET, hasPath);
}
/** /**
* Creates a new decoder that decodes the specified URI encoded in the * Creates a new decoder that decodes the specified URI encoded in the
* specified charset. * specified charset.
*/ */
public QueryStringDecoder(String uri, Charset charset) { public QueryStringDecoder(String uri, Charset charset) {
this(uri, charset, true);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(String uri, Charset charset, boolean hasPath) {
this(uri, charset, hasPath, DEFAULT_MAX_PARAMS);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams) {
if (uri == null) { if (uri == null) {
throw new NullPointerException("uri"); throw new NullPointerException("uri");
} }
if (charset == null) { if (charset == null) {
throw new NullPointerException("charset"); throw new NullPointerException("charset");
} }
if (maxParams <= 0) {
throw new IllegalArgumentException(
"maxParams: " + maxParams + " (expected: a positive integer)");
}
// http://en.wikipedia.org/wiki/Query_string // http://en.wikipedia.org/wiki/Query_string
this.uri = uri.replace(';', '&'); this.uri = uri.replace(';', '&');
this.charset = charset; this.charset = charset;
this.maxParams = maxParams;
this.hasPath = hasPath;
} }
/** /**
@ -86,16 +136,30 @@ public class QueryStringDecoder {
* specified charset. * specified charset.
*/ */
public QueryStringDecoder(URI uri, Charset charset) { public QueryStringDecoder(URI uri, Charset charset) {
this(uri, charset, DEFAULT_MAX_PARAMS);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(URI uri, Charset charset, int maxParams) {
if (uri == null) { if (uri == null) {
throw new NullPointerException("uri"); throw new NullPointerException("uri");
} }
if (charset == null) { if (charset == null) {
throw new NullPointerException("charset"); throw new NullPointerException("charset");
} }
if (maxParams <= 0) {
throw new IllegalArgumentException(
"maxParams: " + maxParams + " (expected: a positive integer)");
}
// http://en.wikipedia.org/wiki/Query_string // http://en.wikipedia.org/wiki/Query_string
this.uri = uri.toASCIIString().replace(';', '&'); this.uri = uri.toASCIIString().replace(';', '&');
this.charset = charset; this.charset = charset;
this.maxParams = maxParams;
hasPath = false;
} }
/** /**
@ -103,11 +167,14 @@ public class QueryStringDecoder {
*/ */
public String getPath() { public String getPath() {
if (path == null) { if (path == null) {
if (!hasPath) {
return path = "";
}
int pathEndPos = uri.indexOf('?'); int pathEndPos = uri.indexOf('?');
if (pathEndPos < 0) { if (pathEndPos < 0) {
path = uri; path = uri;
} } else {
else {
return path = uri.substring(0, pathEndPos); return path = uri.substring(0, pathEndPos);
} }
} }
@ -119,17 +186,25 @@ public class QueryStringDecoder {
*/ */
public Map<String, List<String>> getParameters() { public Map<String, List<String>> getParameters() {
if (params == null) { if (params == null) {
if (hasPath) {
int pathLength = getPath().length(); int pathLength = getPath().length();
if (uri.length() == pathLength) { if (uri.length() == pathLength) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
params = decodeParams(uri.substring(pathLength + 1)); decodeParams(uri.substring(pathLength + 1));
} else {
if (uri.isEmpty()) {
return Collections.emptyMap();
}
decodeParams(uri);
}
} }
return params; return params;
} }
private Map<String, List<String>> decodeParams(String s) { private void decodeParams(String s) {
Map<String, List<String>> params = new LinkedHashMap<String, List<String>>(); Map<String, List<String>> params = this.params = new LinkedHashMap<String, List<String>>();
nParams = 0;
String name = null; String name = null;
int pos = 0; // Beginning of the unprocessed region int pos = 0; // Beginning of the unprocessed region
int i; // End of the unprocessed region int i; // End of the unprocessed region
@ -146,9 +221,13 @@ public class QueryStringDecoder {
// We haven't seen an `=' so far but moved forward. // We haven't seen an `=' so far but moved forward.
// Must be a param of the form '&a&' so add it with // Must be a param of the form '&a&' so add it with
// an empty value. // an empty value.
addParam(params, decodeComponent(s.substring(pos, i), charset), ""); if (!addParam(params, decodeComponent(s.substring(pos, i), charset), "")) {
return;
}
} else if (name != null) { } else if (name != null) {
addParam(params, name, decodeComponent(s.substring(pos, i), charset)); if (!addParam(params, name, decodeComponent(s.substring(pos, i), charset))) {
return;
}
name = null; name = null;
} }
pos = i + 1; pos = i + 1;
@ -157,15 +236,34 @@ public class QueryStringDecoder {
if (pos != i) { // Are there characters we haven't dealt with? if (pos != i) { // Are there characters we haven't dealt with?
if (name == null) { // Yes and we haven't seen any `='. if (name == null) { // Yes and we haven't seen any `='.
addParam(params, decodeComponent(s.substring(pos, i), charset), ""); if (!addParam(params, decodeComponent(s.substring(pos, i), charset), "")) {
return;
}
} else { // Yes and this must be the last value. } else { // Yes and this must be the last value.
addParam(params, name, decodeComponent(s.substring(pos, i), charset)); if (!addParam(params, name, decodeComponent(s.substring(pos, i), charset))) {
return;
}
} }
} else if (name != null) { // Have we seen a name without value? } else if (name != null) { // Have we seen a name without value?
addParam(params, name, ""); if (!addParam(params, name, "")) {
return;
}
}
} }
return params; private boolean addParam(Map<String, List<String>> params, String name, String value) {
if (nParams >= maxParams) {
return false;
}
List<String> values = params.get(name);
if (values == null) {
values = new ArrayList<String>(1); // Often there's only 1 value.
params.put(name, values);
}
values.add(value);
nParams ++;
return true;
} }
/** /**
@ -284,13 +382,4 @@ public class QueryStringDecoder {
return Character.MAX_VALUE; return Character.MAX_VALUE;
} }
} }
private static void addParam(Map<String, List<String>> params, String name, String value) {
List<String> values = params.get(name);
if (values == null) {
values = new ArrayList<String>(1); // Often there's only 1 value.
params.put(name, values);
}
values.add(value);
}
} }

View File

@ -29,7 +29,7 @@ import java.util.List;
* This encoder is for one time use only. Create a new instance for each URI. * This encoder is for one time use only. Create a new instance for each URI.
* *
* <pre> * <pre>
* {@link QueryStringEncoder} encoder = new {@link QueryStringDecoder}("/hello"); * {@link QueryStringEncoder} encoder = new {@link QueryStringEncoder}("/hello");
* encoder.addParam("recipient", "world"); * encoder.addParam("recipient", "world");
* assert encoder.toString().equals("/hello?recipient=world"); * assert encoder.toString().equals("/hello?recipient=world");
* </pre> * </pre>

Some files were not shown because too many files have changed in this diff Show More